cdp-toolkit 1.10.0 → 2.0.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/dist/cli.js CHANGED
@@ -5396,6 +5396,252 @@ async function connectBidiSessionUrl(wsUrl, opts = {}) {
5396
5396
  return conn;
5397
5397
  }
5398
5398
 
5399
+ // src/bidi/session-lease.ts
5400
+ import { mkdir as mkdir12, readFile as readFile8, unlink as unlink5, writeFile as writeFile9 } from "node:fs/promises";
5401
+ import { join as join10 } from "node:path";
5402
+ var DEFAULT_SESSION_WAIT_MS = 1e4;
5403
+ var DEFAULT_POLL_MS = 250;
5404
+ function safeEndpoint(endpoint) {
5405
+ return endpoint.replace(/[^A-Za-z0-9._-]/g, "_");
5406
+ }
5407
+ function sessionSlotFile(endpoint) {
5408
+ return join10(leaseDir(), `ff-session-${safeEndpoint(endpoint)}.json`);
5409
+ }
5410
+ function sessionWaitMs() {
5411
+ const raw = Number(process.env.CDP_FIREFOX_SESSION_WAIT_MS);
5412
+ return Number.isFinite(raw) && raw > 0 ? raw : DEFAULT_SESSION_WAIT_MS;
5413
+ }
5414
+
5415
+ class SessionSlotBusyError extends Error {
5416
+ endpoint;
5417
+ holderPid;
5418
+ holderLabel;
5419
+ constructor(endpoint, holder) {
5420
+ const who = holder ? `'${holder.label}' (pid ${holder.pid})` : "another live process";
5421
+ 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.`);
5422
+ this.name = "SessionSlotBusyError";
5423
+ this.endpoint = endpoint;
5424
+ if (holder) {
5425
+ this.holderPid = holder.pid;
5426
+ this.holderLabel = holder.label;
5427
+ }
5428
+ }
5429
+ }
5430
+ async function readSessionSlot(endpoint) {
5431
+ let raw;
5432
+ try {
5433
+ raw = await readFile8(sessionSlotFile(endpoint), "utf8");
5434
+ } catch (err) {
5435
+ if (err.code === "ENOENT")
5436
+ return;
5437
+ throw err;
5438
+ }
5439
+ try {
5440
+ const rec = JSON.parse(raw);
5441
+ if (typeof rec?.pid === "number" && typeof rec?.endpoint === "string" && typeof rec?.label === "string") {
5442
+ return rec;
5443
+ }
5444
+ } catch {}
5445
+ return;
5446
+ }
5447
+ async function acquireSessionSlot(endpoint, opts) {
5448
+ const waitMs = opts.waitMs ?? sessionWaitMs();
5449
+ const pollMs = opts.pollMs !== undefined && opts.pollMs > 0 ? opts.pollMs : DEFAULT_POLL_MS;
5450
+ const createdAt = opts.now ?? Date.now();
5451
+ const file = sessionSlotFile(endpoint);
5452
+ const record2 = {
5453
+ endpoint,
5454
+ pid: process.pid,
5455
+ label: opts.label,
5456
+ createdAt
5457
+ };
5458
+ if (opts.marionettePort !== undefined)
5459
+ record2.marionettePort = opts.marionettePort;
5460
+ const handle = { endpoint, pid: process.pid, createdAt };
5461
+ const payload = JSON.stringify(record2);
5462
+ await mkdir12(leaseDir(), { recursive: true });
5463
+ const deadline = Date.now() + waitMs;
5464
+ const maxAttempts = Math.max(100, Math.ceil(waitMs / pollMs) + 100);
5465
+ let attempts = 0;
5466
+ let lastLiveHolder;
5467
+ while (true) {
5468
+ if (attempts >= maxAttempts)
5469
+ throw new SessionSlotBusyError(endpoint, lastLiveHolder);
5470
+ attempts++;
5471
+ try {
5472
+ await writeFile9(file, payload, { flag: "wx" });
5473
+ return { handle };
5474
+ } catch (err) {
5475
+ if (err.code !== "EEXIST")
5476
+ throw err;
5477
+ }
5478
+ const holder = await readSessionSlot(endpoint);
5479
+ if (!holder)
5480
+ continue;
5481
+ if (!isPidAlive(holder.pid)) {
5482
+ await unlink5(file).catch(() => {
5483
+ return;
5484
+ });
5485
+ try {
5486
+ await writeFile9(file, payload, { flag: "wx" });
5487
+ return { handle, staleHolder: holder };
5488
+ } catch (err) {
5489
+ if (err.code !== "EEXIST")
5490
+ throw err;
5491
+ continue;
5492
+ }
5493
+ }
5494
+ lastLiveHolder = holder;
5495
+ const remaining = deadline - Date.now();
5496
+ if (remaining <= 0)
5497
+ throw new SessionSlotBusyError(endpoint, holder);
5498
+ await sleep2(Math.min(pollMs, remaining));
5499
+ }
5500
+ }
5501
+ async function releaseSessionSlot(handle) {
5502
+ const current = await readSessionSlot(handle.endpoint);
5503
+ if (!current)
5504
+ return { released: false };
5505
+ if (current.pid !== process.pid || current.createdAt !== handle.createdAt)
5506
+ return { released: false };
5507
+ await unlink5(sessionSlotFile(handle.endpoint)).catch(() => {
5508
+ return;
5509
+ });
5510
+ return { released: true };
5511
+ }
5512
+ function sleep2(ms) {
5513
+ return new Promise((resolve) => setTimeout(resolve, ms));
5514
+ }
5515
+
5516
+ // src/bidi/marionette.ts
5517
+ import { Socket } from "node:net";
5518
+ var DEFAULT_MARIONETTE_PORT = 2828;
5519
+ var DEFAULT_TIMEOUT_MS3 = 4000;
5520
+ var HOST = "127.0.0.1";
5521
+ var COLON = 58;
5522
+ var DELETE_SESSION_FRAME = encodeFrame([0, 1, "WebDriver:DeleteSession", {}]);
5523
+ function defaultMarionettePort() {
5524
+ const raw = Number(process.env.CDP_FIREFOX_MARIONETTE_PORT);
5525
+ return Number.isFinite(raw) && raw > 0 ? raw : DEFAULT_MARIONETTE_PORT;
5526
+ }
5527
+ function encodeFrame(json) {
5528
+ const body = Buffer.from(JSON.stringify(json), "utf8");
5529
+ const prefix = Buffer.from(`${body.length}:`, "ascii");
5530
+ return Buffer.concat([prefix, body]);
5531
+ }
5532
+
5533
+ class MarionetteProtocolError extends Error {
5534
+ constructor(message) {
5535
+ super(message);
5536
+ this.name = "MarionetteProtocolError";
5537
+ }
5538
+ }
5539
+
5540
+ class FrameDecoder {
5541
+ buf = Buffer.alloc(0);
5542
+ push(chunk) {
5543
+ this.buf = this.buf.length === 0 ? chunk : Buffer.concat([this.buf, chunk]);
5544
+ }
5545
+ next() {
5546
+ const colon = this.buf.indexOf(COLON);
5547
+ if (colon === -1) {
5548
+ this.assertDigitsPrefix(this.buf.length);
5549
+ return;
5550
+ }
5551
+ if (colon === 0)
5552
+ throw new MarionetteProtocolError("empty frame length prefix");
5553
+ this.assertDigitsPrefix(colon);
5554
+ const len = Number.parseInt(this.buf.toString("ascii", 0, colon), 10);
5555
+ const start = colon + 1;
5556
+ const end = start + len;
5557
+ if (this.buf.length < end)
5558
+ return;
5559
+ const payload = this.buf.toString("utf8", start, end);
5560
+ this.buf = this.buf.subarray(end);
5561
+ try {
5562
+ return JSON.parse(payload);
5563
+ } catch {
5564
+ throw new MarionetteProtocolError(`frame body was not valid JSON (${len} bytes): ${payload.slice(0, 80)}`);
5565
+ }
5566
+ }
5567
+ assertDigitsPrefix(upTo) {
5568
+ for (let i = 0;i < upTo; i++) {
5569
+ const b = this.buf[i];
5570
+ if (b < 48 || b > 57) {
5571
+ throw new MarionetteProtocolError(`non-digit byte 0x${b.toString(16)} in frame length prefix`);
5572
+ }
5573
+ }
5574
+ }
5575
+ }
5576
+ function isSlotFreedReply(frame) {
5577
+ if (!Array.isArray(frame))
5578
+ return false;
5579
+ const err = frame[2];
5580
+ if (err === null)
5581
+ return true;
5582
+ if (typeof err === "object" && err !== null && err.error === "invalid session id") {
5583
+ return true;
5584
+ }
5585
+ return false;
5586
+ }
5587
+ function forceClearOrphanSession(marionettePort, opts = {}) {
5588
+ const timeoutMs = opts.timeoutMs !== undefined && Number.isFinite(opts.timeoutMs) && opts.timeoutMs > 0 ? opts.timeoutMs : DEFAULT_TIMEOUT_MS3;
5589
+ return new Promise((resolve) => {
5590
+ if (!Number.isFinite(marionettePort) || marionettePort <= 0 || marionettePort > 65535) {
5591
+ resolve(false);
5592
+ return;
5593
+ }
5594
+ const sock = new Socket;
5595
+ const decoder = new FrameDecoder;
5596
+ let settled = false;
5597
+ let handshakeSeen = false;
5598
+ let commandSent = false;
5599
+ const settle = (value) => {
5600
+ if (settled)
5601
+ return;
5602
+ settled = true;
5603
+ clearTimeout(timer);
5604
+ sock.removeAllListeners();
5605
+ sock.destroy();
5606
+ resolve(value);
5607
+ };
5608
+ const timer = setTimeout(() => settle(false), timeoutMs);
5609
+ if (typeof timer.unref === "function")
5610
+ timer.unref();
5611
+ sock.on("error", () => settle(false));
5612
+ sock.on("close", () => settle(false));
5613
+ sock.on("data", (chunk) => {
5614
+ if (settled)
5615
+ return;
5616
+ try {
5617
+ decoder.push(chunk);
5618
+ for (;; ) {
5619
+ const frame = decoder.next();
5620
+ if (frame === undefined)
5621
+ break;
5622
+ if (!handshakeSeen) {
5623
+ handshakeSeen = true;
5624
+ continue;
5625
+ }
5626
+ settle(isSlotFreedReply(frame));
5627
+ return;
5628
+ }
5629
+ if (handshakeSeen && !commandSent) {
5630
+ commandSent = true;
5631
+ sock.write(DELETE_SESSION_FRAME);
5632
+ }
5633
+ } catch {
5634
+ settle(false);
5635
+ }
5636
+ });
5637
+ try {
5638
+ sock.connect(marionettePort, HOST);
5639
+ } catch {
5640
+ settle(false);
5641
+ }
5642
+ });
5643
+ }
5644
+
5399
5645
  // src/bidi/snapshot.ts
5400
5646
  var STAMP_ATTR = "data-cdp-uid";
5401
5647
  async function takeStampedSnapshot(evaluate) {
@@ -5774,6 +6020,9 @@ function mapBidiError(e) {
5774
6020
  return driverError2("timeout", e.message);
5775
6021
  if (!code && /(disposed|not open|connection closed)/i.test(e.message))
5776
6022
  return driverError2("disconnected", e.message);
6023
+ if (/NS_ERROR_FILE_ACCESS_DENIED/i.test(e.message)) {
6024
+ return driverError2("page-error", `${e.message} — the browser process was not allowed to open this file:// path. For a snap- or AppArmor-confined Firefox (Ubuntu default) that is kernel-level and no preference fixes it: serve the directory over a local http server (e.g. python3 -m http.server) and navigate to the http://127.0.0.1:<port>/ URL instead. If this Firefox was launched by cdp-toolkit and still fails, please report it at https://github.com/sblattj/cdp-toolkit/issues.`);
6025
+ }
5777
6026
  return driverError2("page-error", e.message);
5778
6027
  }
5779
6028
  return driverError2("page-error", e instanceof Error ? e.message : String(e));
@@ -5972,6 +6221,22 @@ var BIDI_CAPABILITIES = new Set([
5972
6221
  "locate.xpath"
5973
6222
  ]);
5974
6223
  var connections = new Map;
6224
+ var slotHandles = new Map;
6225
+ var connecting = new Map;
6226
+ async function releaseSlotOnDialFailure(endpoint, owned) {
6227
+ if (owned)
6228
+ return;
6229
+ const h = slotHandles.get(endpoint);
6230
+ if (h) {
6231
+ await releaseSessionSlot(h).catch(() => {
6232
+ return;
6233
+ });
6234
+ slotHandles.delete(endpoint);
6235
+ }
6236
+ }
6237
+ function orphanNotClearedError(endpoint) {
6238
+ 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.)`);
6239
+ }
5975
6240
  async function getConnection(endpoint, timeoutMs) {
5976
6241
  const existing = connections.get(endpoint);
5977
6242
  if (existing && existing.isOpen) {
@@ -5980,17 +6245,68 @@ async function getConnection(endpoint, timeoutMs) {
5980
6245
  }
5981
6246
  if (existing)
5982
6247
  connections.delete(endpoint);
5983
- const conn = await connectBidiSessionUrl(endpoint, {
5984
- ...timeoutMs !== undefined ? { timeoutMs } : {},
5985
- capabilities: { alwaysMatch: { unhandledPromptBehavior: { default: "ignore" } } }
5986
- }).catch((e) => {
5987
- if (e instanceof BidiError && /maximum number of active sessions/i.test(e.message)) {
5988
- throw driverError2("disconnected", `Firefox already has an active WebDriver BiDi session on ${endpoint}; it allows only one. ` + `Close the other client, or restart Firefox, then retry. (A previously killed client can leave a ` + `session that Firefox does not reap.)`);
6248
+ const inflight = connecting.get(endpoint);
6249
+ if (inflight) {
6250
+ const conn = await inflight;
6251
+ conn.retain();
6252
+ return conn;
6253
+ }
6254
+ const create = (async () => {
6255
+ const mport = defaultMarionettePort();
6256
+ const owned = slotHandles.has(endpoint);
6257
+ if (!owned) {
6258
+ let acq;
6259
+ try {
6260
+ acq = await acquireSessionSlot(endpoint, { label: defaultLabel(), marionettePort: mport });
6261
+ } catch (e) {
6262
+ if (e instanceof SessionSlotBusyError) {
6263
+ const who = e.holderLabel ? `'${e.holderLabel}' (pid ${e.holderPid})` : "another live process";
6264
+ 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.`);
6265
+ }
6266
+ throw e;
6267
+ }
6268
+ slotHandles.set(endpoint, acq.handle);
6269
+ if (acq.staleHolder) {
6270
+ await forceClearOrphanSession(acq.staleHolder.marionettePort ?? mport);
6271
+ }
5989
6272
  }
5990
- throw e;
5991
- });
5992
- connections.set(endpoint, conn);
5993
- return conn;
6273
+ const dialOpts = {
6274
+ ...timeoutMs !== undefined ? { timeoutMs } : {},
6275
+ capabilities: { alwaysMatch: { unhandledPromptBehavior: { default: "ignore" } } }
6276
+ };
6277
+ let conn;
6278
+ try {
6279
+ conn = await connectBidiSessionUrl(endpoint, dialOpts);
6280
+ } catch (e) {
6281
+ if (e instanceof BidiError && /maximum number of active sessions/i.test(e.message)) {
6282
+ const cleared = await forceClearOrphanSession(mport);
6283
+ if (!cleared) {
6284
+ await releaseSlotOnDialFailure(endpoint, owned);
6285
+ throw orphanNotClearedError(endpoint);
6286
+ }
6287
+ try {
6288
+ conn = await connectBidiSessionUrl(endpoint, dialOpts);
6289
+ } catch (e2) {
6290
+ await releaseSlotOnDialFailure(endpoint, owned);
6291
+ if (e2 instanceof BidiError && /maximum number of active sessions/i.test(e2.message)) {
6292
+ throw orphanNotClearedError(endpoint);
6293
+ }
6294
+ throw e2;
6295
+ }
6296
+ } else {
6297
+ await releaseSlotOnDialFailure(endpoint, owned);
6298
+ throw e;
6299
+ }
6300
+ }
6301
+ connections.set(endpoint, conn);
6302
+ return conn;
6303
+ })();
6304
+ connecting.set(endpoint, create);
6305
+ try {
6306
+ return await create;
6307
+ } finally {
6308
+ connecting.delete(endpoint);
6309
+ }
5994
6310
  }
5995
6311
  var bidiBeaconScripts = new Map;
5996
6312
  async function safeTitle(conn, contextId) {
@@ -6103,7 +6419,10 @@ class BidiPageDriver {
6103
6419
  const sendOpts = opts.timeoutMs !== undefined ? { timeoutMs: opts.timeoutMs } : {};
6104
6420
  try {
6105
6421
  if (reload) {
6106
- const r2 = await this.conn.send("browsingContext.reload", { context: this.contextId, ignoreCache: opts.ignoreCache === true, wait }, sendOpts);
6422
+ if (opts.ignoreCache === true) {
6423
+ throw driverError2("unsupported", "navigate: ignoreCache:true (hard reload) is not supported on the Firefox WebDriver-BiDi backend — Firefox rejects the ignoreCache argument outright. A plain reload (reload:true, no ignoreCache) works.");
6424
+ }
6425
+ const r2 = await this.conn.send("browsingContext.reload", { context: this.contextId, wait }, sendOpts);
6107
6426
  return { url: r2.url, contextId: this.contextId, reloaded: true, waitedFor: waitUntil };
6108
6427
  }
6109
6428
  if (history) {
@@ -6756,14 +7075,27 @@ class BidiBrowserDriver {
6756
7075
  if (key.startsWith(`${this.endpoint}:`))
6757
7076
  bidiBeaconScripts.delete(key);
6758
7077
  }
7078
+ connecting.delete(this.endpoint);
6759
7079
  const conn = connections.get(this.endpoint);
6760
- if (!conn)
7080
+ if (!conn) {
7081
+ await this.releaseSessionSlotIfHeld();
6761
7082
  return;
7083
+ }
6762
7084
  connections.delete(this.endpoint);
6763
7085
  await conn.send("session.end", {}).catch(() => {
6764
7086
  return;
6765
7087
  });
6766
7088
  conn.dispose();
7089
+ await this.releaseSessionSlotIfHeld();
7090
+ }
7091
+ async releaseSessionSlotIfHeld() {
7092
+ const h = slotHandles.get(this.endpoint);
7093
+ if (!h)
7094
+ return;
7095
+ await releaseSessionSlot(h).catch(() => {
7096
+ return;
7097
+ });
7098
+ slotHandles.delete(this.endpoint);
6767
7099
  }
6768
7100
  }
6769
7101
  function createFirefoxDriver(port, opts) {
@@ -6775,10 +7107,10 @@ function createFirefoxDriverForEndpoint(wsUrl, opts) {
6775
7107
 
6776
7108
  // src/bidi/launch.ts
6777
7109
  import { spawn as spawn3 } from "node:child_process";
6778
- import { existsSync, mkdtempSync, rmSync } from "node:fs";
7110
+ import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
6779
7111
  import { createConnection, createServer } from "node:net";
6780
7112
  import { tmpdir } from "node:os";
6781
- import { join as join10 } from "node:path";
7113
+ import { join as join11 } from "node:path";
6782
7114
  function resolveBinary() {
6783
7115
  const candidates = [
6784
7116
  process.env.FIREFOX_BIN,
@@ -6852,11 +7184,37 @@ function waitForExit(proc, deadlineMs) {
6852
7184
  });
6853
7185
  });
6854
7186
  }
7187
+ var LAUNCH_USER_JS_PREFS = {
7188
+ "remote.prefs.recommended": true,
7189
+ "security.fileuri.strict_origin_policy": false,
7190
+ "dom.file.createInChild": true
7191
+ };
7192
+ function writeLaunchUserJs(profilePath) {
7193
+ const target = join11(profilePath, "user.js");
7194
+ const existing = existsSync(target) ? readFileSync(target, "utf8") : "";
7195
+ const present = new Set;
7196
+ for (const line of existing.split(`
7197
+ `)) {
7198
+ const m = /^user_pref\("([^"]+)"\s*,/.exec(line.trim());
7199
+ if (m)
7200
+ present.add(m[1]);
7201
+ }
7202
+ const additions = Object.entries(LAUNCH_USER_JS_PREFS).filter(([k]) => !present.has(k)).map(([k, v]) => `user_pref("${k}", ${JSON.stringify(v)});`);
7203
+ const content = (existing.length > 0 ? existing.replace(/\n*$/, `
7204
+ `) : "") + additions.join(`
7205
+ `) + (additions.length > 0 ? `
7206
+ ` : "");
7207
+ if (content !== existing)
7208
+ writeFileSync(target, content, "utf-8");
7209
+ return content;
7210
+ }
6855
7211
  async function launchFirefox(opts = {}) {
6856
7212
  const binary = resolveBinary();
6857
7213
  const port = await pickPort(opts.port);
6858
7214
  const isThrowaway = opts.profilePath === undefined;
6859
- const profilePath = opts.profilePath ?? mkdtempSync(join10(tmpdir(), "cdp-toolkit-ff-profile-"));
7215
+ const profilePath = opts.profilePath ?? mkdtempSync(join11(tmpdir(), "cdp-toolkit-ff-profile-"));
7216
+ if (isThrowaway)
7217
+ writeLaunchUserJs(profilePath);
6860
7218
  const args = ["--profile", profilePath, "--remote-debugging-port", String(port), "--no-remote"];
6861
7219
  if (opts.headless)
6862
7220
  args.push("--headless");
@@ -7210,21 +7568,21 @@ var FIREFOX_TOOLS = {
7210
7568
  var MANIFEST = [
7211
7569
  {
7212
7570
  name: "list_pages",
7213
- description: "Enumerate browser page targets (GET /json/list); page-type tabs only unless all=true. Each entry carries its targetId plus an origin field: 'agent' means this toolkit created it (also carries the creating label/createdAt, and stays 'agent' after the lease is released or the creator dies, making stray agent tabs findable), 'unknown' means no creation record - it never says 'human', the honest word for any tab it did not create; a record that exists but won't read adds originUnreadable. An entry under an active, readable lease also carries a 'lease' object {label,pid,idleMs,expiresAt,stale}, with idleMs and expiresAt computed fresh every call (never off disk), independent of probe. Under CDP_REQUIRE_LEASE this call also reaps abandoned agent tabs into an additive reaped array - but a lease reads reclaimable the moment its TTL elapses, while the tab is CLOSED only after a further grace (CDP_REAP_GRACE_MS, default 2700000ms/45min, so 60min total after last use); a dead-pid lease is reaped at once, a tab with no lease never. With probe, each page-type entry also gets 'responsive' (and humanActiveMs where human input is seen); both are absent, never false/null, without probe.",
7571
+ description: "List browser page targets; optionally include non-page targets and probe responsiveness.",
7214
7572
  inputSchema: {
7215
7573
  type: "object",
7216
7574
  properties: {
7217
7575
  lease: {
7218
7576
  type: "string",
7219
- description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
7577
+ description: "Lease token; omit for a tab this process already holds. See server instructions."
7220
7578
  },
7221
7579
  all: {
7222
7580
  type: "boolean",
7223
- description: "Include non-page targets (service/shared workers, background pages); default false lists page-type tabs only."
7581
+ description: "Include non-page targets."
7224
7582
  },
7225
7583
  probe: {
7226
7584
  type: "boolean",
7227
- description: "Ping each page-type renderer before returning, one bounded 500ms check per target and never more, so a wedged tab can't stall the listing. Adds 'responsive' (false on timeout/exception/unreachable, never an error for the whole call) and, where the same round trip finds human input, humanActiveMs. Default false. On a backend that cannot answer the ping, responsive is absent too (not false)."
7585
+ description: "Probe renderer responsiveness."
7228
7586
  }
7229
7587
  },
7230
7588
  required: [],
@@ -7233,29 +7591,29 @@ var MANIFEST = [
7233
7591
  },
7234
7592
  {
7235
7593
  name: "new_page",
7236
- description: "Open a new browser tab via Target.createTarget and return {targetId,url}. Defaults to about:blank; navigation is not awaited here (use navigate_page to load and wait).",
7594
+ description: "Open a browser tab, optionally claim it; navigation is not awaited.",
7237
7595
  inputSchema: {
7238
7596
  type: "object",
7239
7597
  properties: {
7240
7598
  lease: {
7241
7599
  type: "string",
7242
- description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
7600
+ description: "Lease token; omit for a tab this process already holds. See server instructions."
7243
7601
  },
7244
7602
  url: {
7245
7603
  type: "string",
7246
- description: "URL to open in the new tab; defaults to about:blank."
7604
+ description: "URL value."
7247
7605
  },
7248
7606
  claim: {
7249
7607
  type: "boolean",
7250
- description: "Claim the new tab atomically and return a lease token alongside {targetId,url}. Under CDP_REQUIRE_LEASE the tab is claimed and a lease returned even without claim:true; claim:true makes it an EXPLICIT lease that demands its token on every later call, even from this process. Default false."
7608
+ description: "Claim the new tab."
7251
7609
  },
7252
7610
  label: {
7253
7611
  type: "string",
7254
- description: "Agent label recorded on the lease when the tab is claimed; surfaced in conflict errors and list_leases. Defaults to pid-<pid>."
7612
+ description: "Agent lease label."
7255
7613
  },
7256
7614
  ttlMs: {
7257
7615
  type: "number",
7258
- description: "How long a claimed lease survives without use before it is reclaimable. Defaults to CDP_LEASE_TTL_MS, else 900000 (15min); every checked call refreshes it. Ignored when not claiming."
7616
+ description: "Lease TTL in milliseconds."
7259
7617
  }
7260
7618
  },
7261
7619
  required: [],
@@ -7264,17 +7622,17 @@ var MANIFEST = [
7264
7622
  },
7265
7623
  {
7266
7624
  name: "close_page",
7267
- description: "Close a page target via Target.closeTarget. Requires an explicit, resolvable target; errors rather than guessing when the selector matches nothing.",
7625
+ description: "Close a selected page; target must resolve explicitly.",
7268
7626
  inputSchema: {
7269
7627
  type: "object",
7270
7628
  properties: {
7271
7629
  lease: {
7272
7630
  type: "string",
7273
- description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
7631
+ description: "Lease token; omit for a tab this process already holds. See server instructions."
7274
7632
  },
7275
7633
  target: {
7276
7634
  type: "string",
7277
- description: "Page selector: 'active' (default), 'index:N', 'url:<substr>', 'title:<substr>', 'label:<name>', or a 32-hex '<targetId>'. Grammar: see server instructions."
7635
+ description: "Page selector (index:/url:/title:/label:/targetId, default active); grammar in server instructions."
7278
7636
  }
7279
7637
  },
7280
7638
  required: [
@@ -7285,17 +7643,17 @@ var MANIFEST = [
7285
7643
  },
7286
7644
  {
7287
7645
  name: "select_page",
7288
- description: "Activate/focus a page target via Target.activateTarget and persist its bare targetId to the selected-state file (CDP_STATE_DIR/selected). Requires an explicit, resolvable target.",
7646
+ description: "Focus a selected page and persist it as active; target must resolve explicitly.",
7289
7647
  inputSchema: {
7290
7648
  type: "object",
7291
7649
  properties: {
7292
7650
  lease: {
7293
7651
  type: "string",
7294
- description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
7652
+ description: "Lease token; omit for a tab this process already holds. See server instructions."
7295
7653
  },
7296
7654
  target: {
7297
7655
  type: "string",
7298
- description: "Page selector: 'active' (default), 'index:N', 'url:<substr>', 'title:<substr>', 'label:<name>', or a 32-hex '<targetId>'. Grammar: see server instructions."
7656
+ description: "Page selector (index:/url:/title:/label:/targetId, default active); grammar in server instructions."
7299
7657
  }
7300
7658
  },
7301
7659
  required: [
@@ -7306,25 +7664,25 @@ var MANIFEST = [
7306
7664
  },
7307
7665
  {
7308
7666
  name: "navigate_page",
7309
- description: "Navigate a target page to a URL (Page.navigate), reload it (reload:true), OR go back/forward in its session history (history:'back'|'forward'), then wait for the load milestone with a bounded timeout so a wedged renderer can't hang. Exactly one of url / reload / history per call. Returns {url,frameId,waitedFor} (plus reloaded:true on a reload, traversed:'back'|'forward' on a history move); waitUntil supports 'load'|'domcontentloaded' only (no 'networkidle'), and there is no auto-snapshot of the new page. Pass reload:true with ignoreCache:true for a hard reload that refetches every subresource (e.g. to pick up a freshly-deployed, non-content-hashed bundle the HTTP cache would serve stale).",
7667
+ description: "Navigate, reload, or traverse page history; choose exactly one operation.",
7310
7668
  inputSchema: {
7311
7669
  type: "object",
7312
7670
  properties: {
7313
7671
  lease: {
7314
7672
  type: "string",
7315
- description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
7673
+ description: "Lease token; omit for a tab this process already holds. See server instructions."
7316
7674
  },
7317
7675
  target: {
7318
7676
  type: "string",
7319
- description: "Page selector: 'active' (default), 'index:N', 'url:<substr>', 'title:<substr>', 'label:<name>', or a 32-hex '<targetId>'. Grammar: see server instructions."
7677
+ description: "Page selector (index:/url:/title:/label:/targetId, default active); grammar in server instructions."
7320
7678
  },
7321
7679
  url: {
7322
7680
  type: "string",
7323
- description: "Destination URL to navigate to. Required unless reload:true."
7681
+ description: "URL value."
7324
7682
  },
7325
7683
  reload: {
7326
7684
  type: "boolean",
7327
- description: "Reload the current page (Page.reload) instead of navigating to url. Default false."
7685
+ description: "Reload before capture."
7328
7686
  },
7329
7687
  history: {
7330
7688
  type: "string",
@@ -7332,11 +7690,11 @@ var MANIFEST = [
7332
7690
  "back",
7333
7691
  "forward"
7334
7692
  ],
7335
- description: "Traverse this tab's session history instead of loading a url: 'back' and 'forward' are the browser's Back and Forward buttons. Mutually exclusive with 'url' and 'reload' — passing two of the three is refused by name, not resolved by precedence. Works on BOTH backends and waits for the same load milestone as an ordinary navigation. Going back from the first entry, or forward from the last, is an ERROR naming the direction — never a silent no-op. The result carries traversed:'back'|'forward' alongside the resulting url."
7693
+ description: "History direction."
7336
7694
  },
7337
7695
  ignoreCache: {
7338
7696
  type: "boolean",
7339
- description: "On reload, bypass the HTTP cache (hard reload) so subresources are refetched. Ignored unless reload:true. Default false."
7697
+ description: "Bypass cache on reload."
7340
7698
  },
7341
7699
  waitUntil: {
7342
7700
  type: "string",
@@ -7344,11 +7702,11 @@ var MANIFEST = [
7344
7702
  "load",
7345
7703
  "domcontentloaded"
7346
7704
  ],
7347
- description: "Which load milestone to wait for. Defaults to 'load'. No 'networkidle' support."
7705
+ description: "Load milestone."
7348
7706
  },
7349
7707
  timeoutMs: {
7350
7708
  type: "number",
7351
- description: "Override the navigation timeout in milliseconds."
7709
+ description: "Timeout in milliseconds."
7352
7710
  }
7353
7711
  },
7354
7712
  additionalProperties: false
@@ -7356,29 +7714,29 @@ var MANIFEST = [
7356
7714
  },
7357
7715
  {
7358
7716
  name: "wait_for",
7359
- description: "Poll the target until the substring appears in document.body.innerText (Runtime.evaluate on a fixed interval), or throw on timeout. Text-substring only -- no aria/role/selector or event variants; throws rather than returning {found:false}.",
7717
+ description: "Wait for a text substring in document.body.innerText; timeout throws.",
7360
7718
  inputSchema: {
7361
7719
  type: "object",
7362
7720
  properties: {
7363
7721
  lease: {
7364
7722
  type: "string",
7365
- description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
7723
+ description: "Lease token; omit for a tab this process already holds. See server instructions."
7366
7724
  },
7367
7725
  target: {
7368
7726
  type: "string",
7369
- description: "Page selector: 'active' (default), 'index:N', 'url:<substr>', 'title:<substr>', 'label:<name>', or a 32-hex '<targetId>'. Grammar: see server instructions."
7727
+ description: "Page selector (index:/url:/title:/label:/targetId, default active); grammar in server instructions."
7370
7728
  },
7371
7729
  text: {
7372
7730
  type: "string",
7373
- description: "Substring to wait for in document.body.innerText. Required."
7731
+ description: "Text substring."
7374
7732
  },
7375
7733
  timeoutMs: {
7376
7734
  type: "number",
7377
- description: "Total time budget in ms. Default 15000."
7735
+ description: "Timeout in milliseconds."
7378
7736
  },
7379
7737
  pollMs: {
7380
7738
  type: "number",
7381
- description: "Poll interval in ms. Default 250."
7739
+ description: "Polling interval in milliseconds."
7382
7740
  }
7383
7741
  },
7384
7742
  required: [
@@ -7389,37 +7747,37 @@ var MANIFEST = [
7389
7747
  },
7390
7748
  {
7391
7749
  name: "evaluate_script",
7392
- description: "Run arbitrary JavaScript in the target's main-world context over raw CDP and return the evaluated value (returnByValue). With no 'args', 'expression' is a raw expression; with 'args' it must be a function literal (arrow or classic) invoked on globalThis with args passed positionally. A thrown exception surfaces as an error; non-serializable returns (DOM nodes, functions) come back as their CDP description string. savePath writes the value to a JSON file instead -- the response then carries only {path,bytes,type,target} and the value never appears in it, the way to read a credential (a JWT/session token from localStorage) without putting the secret in the transcript. This is the one tool that EVALUATES inside an MV3 extension's background SERVICE WORKER rather than OBSERVING it (Chrome, Capability 'worker.targets'): a worker: target reads chrome.storage.local, chrome.runtime and the worker's own globals directly, with an idle-evicted worker started first (see 'wake').",
7750
+ description: "Evaluate JavaScript in a page or Chrome worker; args require a function literal.",
7393
7751
  inputSchema: {
7394
7752
  type: "object",
7395
7753
  properties: {
7396
7754
  lease: {
7397
7755
  type: "string",
7398
- description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
7756
+ description: "Lease token; omit for a tab this process already holds. See server instructions."
7399
7757
  },
7400
7758
  target: {
7401
7759
  type: "string",
7402
- description: "Page selector: 'active' (default), 'index:N', 'url:<substr>', 'title:<substr>', 'label:<name>', '<targetId>'; plus 'worker:<substring>' to reach a service/shared worker (e.g. an MV3 extension background worker). The worker: arm is CHROME-ONLY (Capability 'worker.targets'). Grammar: see server instructions."
7760
+ description: "Page selector (index:/url:/title:/label:/targetId, default active); grammar in server instructions."
7403
7761
  },
7404
7762
  wake: {
7405
7763
  type: "boolean",
7406
- description: "Worker targets only, when starting a capture. Default true: an idle-evicted MV3 service worker exists in no target listing, so it is started first (ServiceWorker.startWorker) and re-confirmed; false fails fast. Rejected on a page target, a bare target id, or a read-only call. See server instructions."
7764
+ description: "Wake worker before capture."
7407
7765
  },
7408
7766
  expression: {
7409
7767
  type: "string",
7410
- description: "JavaScript to run. Evaluated as an expression when 'args' is omitted; must be a function literal (arrow or classic) whose parameters receive 'args' when provided. REQUIRED -- the code always goes in this key, never in 'function', 'code', 'js', 'script', 'fn' or 'body'; a call using one of those is rejected with an error naming the wrong key."
7768
+ description: "JavaScript expression or function."
7411
7769
  },
7412
7770
  awaitPromise: {
7413
7771
  type: "boolean",
7414
- description: "Await the result if it is a Promise. Default true."
7772
+ description: "Await Promise results."
7415
7773
  },
7416
7774
  args: {
7417
7775
  type: "array",
7418
- description: "Positional JSON-serializable arguments passed to the expression (treated as a function). No live element/page handle is bound."
7776
+ description: "Positional JSON arguments."
7419
7777
  },
7420
7778
  savePath: {
7421
7779
  type: "string",
7422
- description: "Write the evaluated value to this file as JSON and KEEP IT OUT OF THE RESPONSE: the result is {path,bytes,type,target} only, with no copy, preview or truncation of the value -- read credentials without putting them in a transcript. An absolute path (starting with /) is used as-is; a relative path resolves under the artifact dir (/tmp/cdp-toolkit); missing parents are created. Omit to get the value back inline."
7780
+ description: "Output file path."
7423
7781
  }
7424
7782
  },
7425
7783
  required: [
@@ -7430,29 +7788,29 @@ var MANIFEST = [
7430
7788
  },
7431
7789
  {
7432
7790
  name: "list_cookies",
7433
- description: "Read the target page's cookie store INCLUDING httpOnly cookies, which document.cookie (and therefore evaluate_script) cannot see. Page-scoped, not browser-wide: returns cookies of the resolved tab, so point at a page on the site you want. Each cookie carries name, value, domain, path, expires (Unix seconds, -1 for a session cookie), size, httpOnly, secure, sameSite ('strict'|'lax'|'none'|'default'), session. Filter with 'domain' and/or 'name'. Pass 'savePath' to write the array to a JSON file instead: the response is then {path,bytes,count,target} with no cookie value in any form, the way to capture a session cookie without putting the credential in the transcript.",
7791
+ description: "Read cookies for a selected page, including httpOnly values; savePath keeps values out of the response.",
7434
7792
  inputSchema: {
7435
7793
  type: "object",
7436
7794
  properties: {
7437
7795
  lease: {
7438
7796
  type: "string",
7439
- description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
7797
+ description: "Lease token; omit for a tab this process already holds. See server instructions."
7440
7798
  },
7441
7799
  target: {
7442
7800
  type: "string",
7443
- description: "Page selector: 'active' (default), 'index:N', 'url:<substr>', 'title:<substr>', 'label:<name>', or a 32-hex '<targetId>'. Grammar: see server instructions."
7801
+ description: "Page selector (index:/url:/title:/label:/targetId, default active); grammar in server instructions."
7444
7802
  },
7445
7803
  domain: {
7446
7804
  type: "string",
7447
- description: "Keep only cookies for this domain. A leading dot is ignored on both sides and subdomains match too, so 'example.test' matches '.example.test' and 'app.example.test'. No wildcards."
7805
+ description: "Cookie domain."
7448
7806
  },
7449
7807
  name: {
7450
7808
  type: "string",
7451
- description: "Keep only the cookie with exactly this name. Exact match, not a substring."
7809
+ description: "Cookie name."
7452
7810
  },
7453
7811
  savePath: {
7454
7812
  type: "string",
7455
- description: "Write the cookie array here as JSON and KEEP THE VALUES OUT OF THE RESPONSE: the result is {path,bytes,count,target} only, with no preview or truncation of any value. An absolute path (starting with /) is used as-is; a relative path resolves under the artifact dir (/tmp/cdp-toolkit); missing parents are created. Omit to get the cookies back inline."
7813
+ description: "Output file path."
7456
7814
  }
7457
7815
  },
7458
7816
  required: [],
@@ -7461,49 +7819,49 @@ var MANIFEST = [
7461
7819
  },
7462
7820
  {
7463
7821
  name: "set_cookie",
7464
- description: "Write one cookie into the target page's cookie store, INCLUDING httpOnly or secure cookies that document.cookie (and therefore evaluate_script) cannot create. Either 'url' or 'domain' is REQUIRED: the call is refused when neither is given, rather than guessing the page origin. The response is {set:true,target} and never echoes the value back. A 'set:true' is earned, not assumed: Chrome raises an error when it declines the cookie (a domain the url does not belong to, a secure cookie on an insecure origin, an oversized value). On Firefox 'domain' is derived from 'url' when only the url was given (BiDi has no url parameter), and a url with no host such as about:blank or a data URL is an error, not a silent no-op. Read the result back with list_cookies when you need proof.",
7822
+ description: "Set one page cookie, including httpOnly or secure flags; provide url or domain.",
7465
7823
  inputSchema: {
7466
7824
  type: "object",
7467
7825
  properties: {
7468
7826
  lease: {
7469
7827
  type: "string",
7470
- description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
7828
+ description: "Lease token; omit for a tab this process already holds. See server instructions."
7471
7829
  },
7472
7830
  target: {
7473
7831
  type: "string",
7474
- description: "Page selector: 'active' (default), 'index:N', 'url:<substr>', 'title:<substr>', 'label:<name>', or a 32-hex '<targetId>'. Grammar: see server instructions."
7832
+ description: "Page selector (index:/url:/title:/label:/targetId, default active); grammar in server instructions."
7475
7833
  },
7476
7834
  name: {
7477
7835
  type: "string",
7478
- description: "Cookie name. Required and non-empty."
7836
+ description: "Cookie name."
7479
7837
  },
7480
7838
  value: {
7481
7839
  type: "string",
7482
- description: "Cookie value. Required. It is not echoed back in the response."
7840
+ description: "Cookie value."
7483
7841
  },
7484
7842
  url: {
7485
7843
  type: "string",
7486
- description: "URL the cookie is set for, e.g. 'https://example.com/'. Chrome derives domain, path and secure from it; Firefox derives only the domain from its host. Give this or 'domain'."
7844
+ description: "URL value."
7487
7845
  },
7488
7846
  domain: {
7489
7847
  type: "string",
7490
- description: "Cookie domain, e.g. 'example.com' or '.example.com' for subdomains. Give this or 'url'. When both are given, this wins on Firefox and Chrome applies its own url plus domain consistency rule."
7848
+ description: "Cookie domain."
7491
7849
  },
7492
7850
  path: {
7493
7851
  type: "string",
7494
- description: "Cookie path, passed through exactly as given and NOT defaulted to '/': with a 'url' Chrome derives the path from it, so an invented default here would widen a cookie you meant to scope narrowly."
7852
+ description: "Cookie path."
7495
7853
  },
7496
7854
  expires: {
7497
7855
  type: "number",
7498
- description: "Expiry as Unix time in SECONDS, matching what list_cookies reports. Omit for a session cookie, which is what both backends do when no expiry is supplied."
7856
+ description: "Expiry Unix time in seconds."
7499
7857
  },
7500
7858
  httpOnly: {
7501
7859
  type: "boolean",
7502
- description: "Set the httpOnly flag, making the cookie invisible to document.cookie. Only a protocol write like this one can create such a cookie."
7860
+ description: "Set HttpOnly flag."
7503
7861
  },
7504
7862
  secure: {
7505
7863
  type: "boolean",
7506
- description: "Set the secure flag. Chrome declines a secure cookie on an insecure origin, and that refusal surfaces as an error."
7864
+ description: "Set Secure flag."
7507
7865
  },
7508
7866
  sameSite: {
7509
7867
  type: "string",
@@ -7513,7 +7871,7 @@ var MANIFEST = [
7513
7871
  "none",
7514
7872
  "default"
7515
7873
  ],
7516
- description: "SameSite attribute, lowercase, matching what list_cookies reports. 'default' means the attribute is not set at all."
7874
+ description: "SameSite attribute."
7517
7875
  }
7518
7876
  },
7519
7877
  required: [
@@ -7525,33 +7883,33 @@ var MANIFEST = [
7525
7883
  },
7526
7884
  {
7527
7885
  name: "delete_cookies",
7528
- description: "Delete the named cookie from the target page's cookie store, httpOnly cookies included. Both 'name' and one of 'url' or 'domain' are REQUIRED: without a site constraint it would delete by name across the whole partition, so it is refused with an error instead. Narrow further with 'path'. The response is {deleted:true,target} and carries NO count, because neither protocol reports how many cookies it removed and a number here would be invented; call list_cookies before and after for a real count. 'deleted:true' means the backend accepted and performed the deletion, not that a matching cookie existed, since deleting an absent cookie succeeds on both backends.",
7886
+ description: "Delete a named page cookie; provide name and url or domain.",
7529
7887
  inputSchema: {
7530
7888
  type: "object",
7531
7889
  properties: {
7532
7890
  lease: {
7533
7891
  type: "string",
7534
- description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
7892
+ description: "Lease token; omit for a tab this process already holds. See server instructions."
7535
7893
  },
7536
7894
  target: {
7537
7895
  type: "string",
7538
- description: "Page selector: 'active' (default), 'index:N', 'url:<substr>', 'title:<substr>', 'label:<name>', or a 32-hex '<targetId>'. Grammar: see server instructions."
7896
+ description: "Page selector (index:/url:/title:/label:/targetId, default active); grammar in server instructions."
7539
7897
  },
7540
7898
  name: {
7541
7899
  type: "string",
7542
- description: "Exact cookie name to delete. Required and non-empty. No wildcards: this removes a named cookie, not a swathe of them."
7900
+ description: "Cookie name."
7543
7901
  },
7544
7902
  url: {
7545
7903
  type: "string",
7546
- description: "Delete cookies matching this URL, e.g. 'https://example.com/'. Give this or 'domain'. On Firefox only the host is used, because BiDi's filter has no url field."
7904
+ description: "URL value."
7547
7905
  },
7548
7906
  domain: {
7549
7907
  type: "string",
7550
- description: "Delete cookies with this exact domain. Give this or 'url'."
7908
+ description: "Cookie domain."
7551
7909
  },
7552
7910
  path: {
7553
7911
  type: "string",
7554
- description: "Narrow the deletion to cookies with this exact path. Omit to match any path allowed by the other constraints."
7912
+ description: "Cookie path."
7555
7913
  }
7556
7914
  },
7557
7915
  required: [
@@ -7562,21 +7920,21 @@ var MANIFEST = [
7562
7920
  },
7563
7921
  {
7564
7922
  name: "take_snapshot",
7565
- description: "Captures the page's accessibility tree as an indented text tree, each line prefixed [uid] (a CDP backendDOMNodeId). Every interaction tool (click/hover/fill/etc.) resolves that uid via DOM.resolveNode — run this first to discover uids to act on.",
7923
+ description: "Capture the page accessibility tree with interaction UIDs.",
7566
7924
  inputSchema: {
7567
7925
  type: "object",
7568
7926
  properties: {
7569
7927
  lease: {
7570
7928
  type: "string",
7571
- description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
7929
+ description: "Lease token; omit for a tab this process already holds. See server instructions."
7572
7930
  },
7573
7931
  target: {
7574
7932
  type: "string",
7575
- description: "Page selector: 'active' (default), 'index:N', 'url:<substr>', 'title:<substr>', 'label:<name>', or a 32-hex '<targetId>'. Grammar: see server instructions."
7933
+ description: "Page selector (index:/url:/title:/label:/targetId, default active); grammar in server instructions."
7576
7934
  },
7577
7935
  interactiveOnly: {
7578
7936
  type: "boolean",
7579
- description: "When true, emit only interactive/meaningful nodes as a flat list; default false returns the full hierarchical tree."
7937
+ description: "Only interactive nodes."
7580
7938
  }
7581
7939
  },
7582
7940
  required: [],
@@ -7585,25 +7943,25 @@ var MANIFEST = [
7585
7943
  },
7586
7944
  {
7587
7945
  name: "click",
7588
- description: "Click an element via a synthetic mouse press/release at its scrolled-into-view center. Give exactly one of uid or selector. modifiers (Alt/Control/Meta/Shift) throws on the Firefox backend — use --browser chrome for a modifier-click. clickCount:3 triple-clicks (selects a line/paragraph in most editors).",
7946
+ description: "Click an element by UID or CSS selector; provide exactly one.",
7589
7947
  inputSchema: {
7590
7948
  type: "object",
7591
7949
  properties: {
7592
7950
  lease: {
7593
7951
  type: "string",
7594
- description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
7952
+ description: "Lease token; omit for a tab this process already holds. See server instructions."
7595
7953
  },
7596
7954
  target: {
7597
7955
  type: "string",
7598
- description: "Page selector: 'active' (default), 'index:N', 'url:<substr>', 'title:<substr>', 'label:<name>', or a 32-hex '<targetId>'. Grammar: see server instructions."
7956
+ description: "Page selector (index:/url:/title:/label:/targetId, default active); grammar in server instructions."
7599
7957
  },
7600
7958
  uid: {
7601
7959
  type: "number",
7602
- description: "CDP backendDOMNodeId of the element to click, from take_snapshot. Exactly one of uid or selector."
7960
+ description: "Element backendDOMNodeId."
7603
7961
  },
7604
7962
  selector: {
7605
7963
  type: "string",
7606
- description: "CSS selector for the element to click (document.querySelector). Exactly one of uid or selector."
7964
+ description: "CSS selector."
7607
7965
  },
7608
7966
  button: {
7609
7967
  type: "string",
@@ -7612,11 +7970,11 @@ var MANIFEST = [
7612
7970
  "right",
7613
7971
  "middle"
7614
7972
  ],
7615
- description: "Mouse button: 'left' (default), 'right', or 'middle'."
7973
+ description: "Mouse button."
7616
7974
  },
7617
7975
  clickCount: {
7618
7976
  type: "number",
7619
- description: "Number of clicks: 1 (default), 2 = double-click, 3 = triple-click."
7977
+ description: "Click count."
7620
7978
  },
7621
7979
  modifiers: {
7622
7980
  type: "array",
@@ -7629,7 +7987,7 @@ var MANIFEST = [
7629
7987
  "Shift"
7630
7988
  ]
7631
7989
  },
7632
- description: "Modifier keys held for the click's press and release, e.g. ['Shift']. Non-empty on the Firefox backend throws — not supported over BiDi."
7990
+ description: "Modifier keys: Control|Ctrl|Shift|Alt|Meta|Cmd."
7633
7991
  }
7634
7992
  },
7635
7993
  required: [],
@@ -7638,25 +7996,25 @@ var MANIFEST = [
7638
7996
  },
7639
7997
  {
7640
7998
  name: "hover",
7641
- description: "Hovers the mouse over an element by dispatching a mouseMoved event at its scrolled-into-view center, firing framework hover handlers. Give exactly one of uid or selector.",
7999
+ description: "Hover an element by UID or CSS selector; provide exactly one.",
7642
8000
  inputSchema: {
7643
8001
  type: "object",
7644
8002
  properties: {
7645
8003
  lease: {
7646
8004
  type: "string",
7647
- description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
8005
+ description: "Lease token; omit for a tab this process already holds. See server instructions."
7648
8006
  },
7649
8007
  target: {
7650
8008
  type: "string",
7651
- description: "Page selector: 'active' (default), 'index:N', 'url:<substr>', 'title:<substr>', 'label:<name>', or a 32-hex '<targetId>'. Grammar: see server instructions."
8009
+ description: "Page selector (index:/url:/title:/label:/targetId, default active); grammar in server instructions."
7652
8010
  },
7653
8011
  uid: {
7654
8012
  type: "number",
7655
- description: "CDP backendDOMNodeId of the element to hover, from take_snapshot. Exactly one of uid or selector."
8013
+ description: "Element backendDOMNodeId."
7656
8014
  },
7657
8015
  selector: {
7658
8016
  type: "string",
7659
- description: "CSS selector for the element to hover (document.querySelector). Exactly one of uid or selector."
8017
+ description: "CSS selector."
7660
8018
  }
7661
8019
  },
7662
8020
  required: [],
@@ -7665,67 +8023,67 @@ var MANIFEST = [
7665
8023
  },
7666
8024
  {
7667
8025
  name: "drag",
7668
- description: "Drags from a source element to a destination or by an offset. mode:'mouse' (default) dispatches synthetic mouse press/move/release; at the default steps:2 an HTML5 drop zone (dragover preventDefault) gets ZERO dragover events and refuses the drop. mode:'html5' replays real dragstart/dragEnter/dragOver/drop with the page's own dataTransfer instead — deterministic regardless of pointer path, but CHROME-ONLY (rejected with a clear error on the Firefox backend). 'from' takes uid or selector; destination is exactly one of 'to' (uid/selector/x+y) or 'by' ({dx,dy} offset — sliders, map panning, resize handles).",
8026
+ description: "Drag from an element to a destination or offset; html5 mode is Chrome-only.",
7669
8027
  inputSchema: {
7670
8028
  type: "object",
7671
8029
  properties: {
7672
8030
  lease: {
7673
8031
  type: "string",
7674
- description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
8032
+ description: "Lease token; omit for a tab this process already holds. See server instructions."
7675
8033
  },
7676
8034
  target: {
7677
8035
  type: "string",
7678
- description: "Page selector: 'active' (default), 'index:N', 'url:<substr>', 'title:<substr>', 'label:<name>', or a 32-hex '<targetId>'. Grammar: see server instructions."
8036
+ description: "Page selector (index:/url:/title:/label:/targetId, default active); grammar in server instructions."
7679
8037
  },
7680
8038
  from: {
7681
8039
  type: "object",
7682
- description: "Source element to drag from. Exactly one of uid or selector.",
8040
+ description: "Drag source.",
7683
8041
  properties: {
7684
8042
  uid: {
7685
8043
  type: "number",
7686
- description: "CDP backendDOMNodeId of the source element, obtained from take_snapshot."
8044
+ description: "Element backendDOMNodeId."
7687
8045
  },
7688
8046
  selector: {
7689
8047
  type: "string",
7690
- description: "CSS selector for the source element (resolved via document.querySelector)."
8048
+ description: "CSS selector."
7691
8049
  }
7692
8050
  },
7693
8051
  additionalProperties: false
7694
8052
  },
7695
8053
  to: {
7696
8054
  type: "object",
7697
- description: "Where the drag ends: exactly one of uid, selector, or x+y. Mutually exclusive with 'by'; exactly one of to/by required.",
8055
+ description: "Drag destination.",
7698
8056
  properties: {
7699
8057
  uid: {
7700
8058
  type: "number",
7701
- description: "CDP backendDOMNodeId of the destination element, obtained from take_snapshot."
8059
+ description: "Element backendDOMNodeId."
7702
8060
  },
7703
8061
  selector: {
7704
8062
  type: "string",
7705
- description: "CSS selector for the destination element (resolved via document.querySelector)."
8063
+ description: "CSS selector."
7706
8064
  },
7707
8065
  x: {
7708
8066
  type: "number",
7709
- description: "Absolute viewport x-coordinate to drop at. Must be given together with 'y'. Use this when there is no droppable element to name (a canvas, a slider track)."
8067
+ description: "Viewport x-coordinate."
7710
8068
  },
7711
8069
  y: {
7712
8070
  type: "number",
7713
- description: "Absolute viewport y-coordinate to drop at. Must be given together with 'x'."
8071
+ description: "Viewport y-coordinate."
7714
8072
  }
7715
8073
  },
7716
8074
  additionalProperties: false
7717
8075
  },
7718
8076
  by: {
7719
8077
  type: "object",
7720
- description: "Offset from the source element's center: at least one of dx/dy (other defaults to 0). Mutually exclusive with 'to'; exactly one of to/by required. For sliders (by:{dx:40}), map panning, resize handles.",
8078
+ description: "Drag offset.",
7721
8079
  properties: {
7722
8080
  dx: {
7723
8081
  type: "number",
7724
- description: "Horizontal offset in CSS pixels; positive drags RIGHT."
8082
+ description: "Horizontal drag offset."
7725
8083
  },
7726
8084
  dy: {
7727
8085
  type: "number",
7728
- description: "Vertical offset in CSS pixels; positive drags DOWN."
8086
+ description: "Vertical drag offset."
7729
8087
  }
7730
8088
  },
7731
8089
  additionalProperties: false
@@ -7736,11 +8094,11 @@ var MANIFEST = [
7736
8094
  "mouse",
7737
8095
  "html5"
7738
8096
  ],
7739
- description: "'mouse' (default): synthetic mouse press/move/release — at steps:2 an HTML5 drop zone sees zero dragover events and refuses the drop. 'html5': real HTML5 drag-and-drop, deterministic regardless of pointer path, CHROME-ONLY (rejected on Firefox)."
8097
+ description: "Drag mode."
7740
8098
  },
7741
8099
  steps: {
7742
8100
  type: "number",
7743
- description: "Number of interpolated mouse-move events between source and destination, evenly spaced, last landing on the destination. Integer 1-500, default 2. Raise for DnD libraries with a movement threshold or per-frame sampling."
8101
+ description: "Interpolation steps."
7744
8102
  }
7745
8103
  },
7746
8104
  required: [
@@ -7751,41 +8109,41 @@ var MANIFEST = [
7751
8109
  },
7752
8110
  {
7753
8111
  name: "scroll",
7754
- description: "Dispatches a wheel/scroll event at an anchor point — element (uid/selector) or x+y, scrolled into view first if an element; omit all three for viewport center. At least one of deltaX/deltaY required: positive deltaY scrolls DOWN, positive deltaX scrolls RIGHT (wheel-event convention — easy to invert). Returns the resolved anchor {x,y} plus the delta actually dispatched.",
8112
+ description: "Dispatch a wheel event at an element or coordinate anchor; provide deltaX or deltaY.",
7755
8113
  inputSchema: {
7756
8114
  type: "object",
7757
8115
  properties: {
7758
8116
  lease: {
7759
8117
  type: "string",
7760
- description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
8118
+ description: "Lease token; omit for a tab this process already holds. See server instructions."
7761
8119
  },
7762
8120
  target: {
7763
8121
  type: "string",
7764
- description: "Page selector: 'active' (default), 'index:N', 'url:<substr>', 'title:<substr>', 'label:<name>', or a 32-hex '<targetId>'. Grammar: see server instructions."
8122
+ description: "Page selector (index:/url:/title:/label:/targetId, default active); grammar in server instructions."
7765
8123
  },
7766
8124
  uid: {
7767
8125
  type: "number",
7768
- description: "CDP backendDOMNodeId of the element to scroll into view and anchor at, from take_snapshot. At most one of uid, selector, or x+y."
8126
+ description: "Element backendDOMNodeId."
7769
8127
  },
7770
8128
  selector: {
7771
8129
  type: "string",
7772
- description: "CSS selector for the element to anchor at (document.querySelector). At most one of uid, selector, or x+y."
8130
+ description: "CSS selector."
7773
8131
  },
7774
8132
  x: {
7775
8133
  type: "number",
7776
- description: "Absolute viewport x-coordinate to anchor at; must be given with y. At most one of uid, selector, or x+y."
8134
+ description: "Viewport x-coordinate."
7777
8135
  },
7778
8136
  y: {
7779
8137
  type: "number",
7780
- description: "Absolute viewport y-coordinate to anchor at; must be given with x. At most one of uid, selector, or x+y."
8138
+ description: "Viewport y-coordinate."
7781
8139
  },
7782
8140
  deltaX: {
7783
8141
  type: "number",
7784
- description: "Horizontal scroll delta; positive scrolls RIGHT. At least one of deltaX/deltaY required."
8142
+ description: "Horizontal scroll delta."
7785
8143
  },
7786
8144
  deltaY: {
7787
8145
  type: "number",
7788
- description: "Vertical scroll delta; positive scrolls DOWN. At least one of deltaX/deltaY required."
8146
+ description: "Vertical scroll delta."
7789
8147
  }
7790
8148
  },
7791
8149
  required: [],
@@ -7794,17 +8152,17 @@ var MANIFEST = [
7794
8152
  },
7795
8153
  {
7796
8154
  name: "dispatch_mouse",
7797
- description: "Dispatches one raw mouse event (move/down/up) at absolute viewport coordinates — the toolkit's lowest-level input primitive. Compose move/down/move/up calls yourself for anything click/drag's fixed sequences can't reach: canvas drag-painting, marquee/rubber-band selection, custom hit-testing widgets. Chrome-only: absent from tools/list under the Firefox backend, never present-and-throwing.",
8155
+ description: "Dispatch one raw mouse event at viewport coordinates; Chrome-only.",
7798
8156
  inputSchema: {
7799
8157
  type: "object",
7800
8158
  properties: {
7801
8159
  lease: {
7802
8160
  type: "string",
7803
- description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
8161
+ description: "Lease token; omit for a tab this process already holds. See server instructions."
7804
8162
  },
7805
8163
  target: {
7806
8164
  type: "string",
7807
- description: "Page selector: 'active' (default), 'index:N', 'url:<substr>', 'title:<substr>', 'label:<name>', or a 32-hex '<targetId>'. Grammar: see server instructions."
8165
+ description: "Page selector (index:/url:/title:/label:/targetId, default active); grammar in server instructions."
7808
8166
  },
7809
8167
  action: {
7810
8168
  type: "string",
@@ -7813,15 +8171,15 @@ var MANIFEST = [
7813
8171
  "down",
7814
8172
  "up"
7815
8173
  ],
7816
- description: "Which raw event to dispatch: 'move' (mouseMoved), 'down' (mousePressed), or 'up' (mouseReleased)."
8174
+ description: "Mouse action."
7817
8175
  },
7818
8176
  x: {
7819
8177
  type: "number",
7820
- description: "Viewport x-coordinate. Required every call — CDP has no 'current pointer position' to default from."
8178
+ description: "Viewport x-coordinate."
7821
8179
  },
7822
8180
  y: {
7823
8181
  type: "number",
7824
- description: "Viewport y-coordinate. Required every call."
8182
+ description: "Viewport y-coordinate."
7825
8183
  },
7826
8184
  button: {
7827
8185
  type: "string",
@@ -7830,11 +8188,11 @@ var MANIFEST = [
7830
8188
  "right",
7831
8189
  "middle"
7832
8190
  ],
7833
- description: "Mouse button: 'left' (default), 'right', or 'middle'."
8191
+ description: "Mouse button."
7834
8192
  },
7835
8193
  clickCount: {
7836
8194
  type: "number",
7837
- description: "Click-run length for a 'down' or 'up' event (2 = second half of a double-click, 3 = triple-click). Ignored for 'move'; defaults to 1."
8195
+ description: "Click count."
7838
8196
  },
7839
8197
  modifiers: {
7840
8198
  type: "array",
@@ -7847,7 +8205,7 @@ var MANIFEST = [
7847
8205
  "Shift"
7848
8206
  ]
7849
8207
  },
7850
- description: "Modifier keys held for this one event, e.g. ['Shift']."
8208
+ description: "Modifier keys: Control|Ctrl|Shift|Alt|Meta|Cmd."
7851
8209
  }
7852
8210
  },
7853
8211
  required: [
@@ -7860,25 +8218,25 @@ var MANIFEST = [
7860
8218
  },
7861
8219
  {
7862
8220
  name: "wait_for_download",
7863
- description: "Wait for a file download to finish and return it as a real file on disk: {path,suggestedFilename,bytes,url,target}, written under the artifact dir's downloads/. ORDERING RULE, not optional: capture must be ARMED BEFORE the click that starts the download — call wait_for_download{arm:true} first (returns {armed:true,downloadPath,pending}), then click, then call again to collect the file. The download-behavior override is per-connection state Chrome REVERTS the moment the arming client disconnects, and an unarmed headless Chrome denies the download outright, so one triggered before arming is lost. SIDE EFFECT, browser-global: arming redirects EVERY download in this browser (all tabs, all origins) into the toolkit's downloads dir until this server exits — they no longer land in the user's normal Downloads folder. The arm lives on a connection this server holds open (an MCP-server capability): under the one-shot CLI the connection dies with the process and nothing is captured. Chrome-only (capability 'browser.downloads'): absent from tools/list under Firefox.",
8221
+ description: "Arm or collect a browser download; arm capture before triggering the download.",
7864
8222
  inputSchema: {
7865
8223
  type: "object",
7866
8224
  properties: {
7867
8225
  lease: {
7868
8226
  type: "string",
7869
- description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
8227
+ description: "Lease token; omit for a tab this process already holds. See server instructions."
7870
8228
  },
7871
8229
  target: {
7872
8230
  type: "string",
7873
- description: "Names the tab for the lease check and the echoed target — a download is browser-scoped, so this does not pick which download is returned. Grammar: server instructions."
8231
+ description: "Page selector (index:/url:/title:/label:/targetId, default active); grammar in server instructions."
7874
8232
  },
7875
8233
  arm: {
7876
8234
  type: "boolean",
7877
- description: "Arm download capture and return immediately instead of waiting: {armed:true,downloadPath,pending}. Call this BEFORE the click that triggers the download. 'pending' counts downloads that already completed and have not been collected yet, so an arm call doubles as a peek."
8235
+ description: "Arm download capture."
7878
8236
  },
7879
8237
  timeoutMs: {
7880
8238
  type: "number",
7881
- description: "How long to wait for a download to complete, in milliseconds. Default 30000. Ignored with arm:true. A download that already completed and was not yet collected is returned immediately, so this only bounds the wait for one still in flight."
8239
+ description: "Timeout in milliseconds."
7882
8240
  }
7883
8241
  },
7884
8242
  required: [],
@@ -7887,32 +8245,32 @@ var MANIFEST = [
7887
8245
  },
7888
8246
  {
7889
8247
  name: "grant_permissions",
7890
- description: "Grant browser permissions for an origin up front, so a page asking for geolocation / notifications / clipboard gets an answer instead of a prompt no agent can click. 'permissions' takes CDP PermissionType values ('geolocation','notifications','clipboardReadWrite','camera',...); an unknown name is refused by Chrome, quoting the bad value. Returns {granted,origin,target}, or {reset:true,target} for a reset-only call. Grants are keyed by ORIGIN, not by tab: every tab on that origin is affected, including ones opened later. reset:true clears this server's previous grants first (or instead, when no permissions are given) — CDP's reset is not origin-scoped, so it clears them for every origin at once. The grant lives on a connection this server holds open and Chrome DISCARDS IT when that connection closes: an MCP-server capability, so under the one-shot CLI the grant dies with the process. Chrome-only (capability 'browser.permissions'): absent from tools/list under Firefox, never present-and-throwing.",
8248
+ description: "Grant or reset browser permissions; permissions are origin-scoped and Chrome-only.",
7891
8249
  inputSchema: {
7892
8250
  type: "object",
7893
8251
  properties: {
7894
8252
  lease: {
7895
8253
  type: "string",
7896
- description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
8254
+ description: "Lease token; omit for a tab this process already holds. See server instructions."
7897
8255
  },
7898
8256
  target: {
7899
8257
  type: "string",
7900
- description: "Also the origin source when 'origin' is omitted. Grammar: server instructions."
8258
+ description: "Page selector (index:/url:/title:/label:/targetId, default active); grammar in server instructions."
7901
8259
  },
7902
8260
  permissions: {
7903
8261
  type: "array",
7904
8262
  items: {
7905
8263
  type: "string"
7906
8264
  },
7907
- description: "CDP PermissionType values to grant, e.g. ['geolocation'] or ['clipboardReadWrite','clipboardSanitizedWrite']. Required unless reset:true. An empty array is refused rather than treated as a successful no-op."
8265
+ description: "CDP PermissionType values, e.g. geolocation|notifications|clipboardReadWrite."
7908
8266
  },
7909
8267
  origin: {
7910
8268
  type: "string",
7911
- description: "Origin the grant applies to, e.g. 'https://example.com'. Defaults to the target tab's own origin. Required explicitly when that tab has no grantable origin (data:, blob:, about:blank all serialize to the opaque origin 'null'), which is refused with a message naming the tab's url."
8269
+ description: "Permission origin."
7912
8270
  },
7913
8271
  reset: {
7914
8272
  type: "boolean",
7915
- description: "Clear this server's previous permission grants (Browser.resetPermissions). With 'permissions' it resets FIRST and then grants, so the result is exactly the listed permissions; on its own it is a reset-only call answering {reset:true}. Not scoped to 'origin': CDP resets every origin at once."
8273
+ description: "Reset permission grants."
7916
8274
  }
7917
8275
  },
7918
8276
  required: [],
@@ -7921,25 +8279,25 @@ var MANIFEST = [
7921
8279
  },
7922
8280
  {
7923
8281
  name: "fill",
7924
- description: "Fills an element by focusing it, clearing existing content, then inserting value via Input.insertText (atomic paste-like commit, not per-character keystrokes — won't fire per-keypress handlers). Give exactly one of uid or selector.",
8282
+ description: "Replace an element contents; provide exactly one of UID or selector.",
7925
8283
  inputSchema: {
7926
8284
  type: "object",
7927
8285
  properties: {
7928
8286
  lease: {
7929
8287
  type: "string",
7930
- description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
8288
+ description: "Lease token; omit for a tab this process already holds. See server instructions."
7931
8289
  },
7932
8290
  target: {
7933
8291
  type: "string",
7934
- description: "Page selector: 'active' (default), 'index:N', 'url:<substr>', 'title:<substr>', 'label:<name>', or a 32-hex '<targetId>'. Grammar: see server instructions."
8292
+ description: "Page selector (index:/url:/title:/label:/targetId, default active); grammar in server instructions."
7935
8293
  },
7936
8294
  uid: {
7937
8295
  type: "number",
7938
- description: "CDP backendDOMNodeId of the field to fill, from take_snapshot. Exactly one of uid or selector."
8296
+ description: "Element backendDOMNodeId."
7939
8297
  },
7940
8298
  selector: {
7941
8299
  type: "string",
7942
- description: "CSS selector for the field to fill (document.querySelector). Exactly one of uid or selector."
8300
+ description: "CSS selector."
7943
8301
  },
7944
8302
  value: {
7945
8303
  type: "string",
@@ -7954,35 +8312,35 @@ var MANIFEST = [
7954
8312
  },
7955
8313
  {
7956
8314
  name: "fill_form",
7957
- description: "Fills multiple form fields in one call; each is focused, cleared, then set via Input.insertText (overwrites existing content, same as fill). Each entry in the non-empty 'fields' array takes exactly one of uid or selector plus a string value.",
8315
+ description: "Fill multiple elements atomically; each field needs a UID or selector.",
7958
8316
  inputSchema: {
7959
8317
  type: "object",
7960
8318
  properties: {
7961
8319
  lease: {
7962
8320
  type: "string",
7963
- description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
8321
+ description: "Lease token; omit for a tab this process already holds. See server instructions."
7964
8322
  },
7965
8323
  target: {
7966
8324
  type: "string",
7967
- description: "Page selector: 'active' (default), 'index:N', 'url:<substr>', 'title:<substr>', 'label:<name>', or a 32-hex '<targetId>'. Grammar: see server instructions."
8325
+ description: "Page selector (index:/url:/title:/label:/targetId, default active); grammar in server instructions."
7968
8326
  },
7969
8327
  fields: {
7970
8328
  type: "array",
7971
- description: "Non-empty array of fields to fill, each with exactly one of uid or selector plus a string value.",
8329
+ description: "Form fields to fill.",
7972
8330
  items: {
7973
8331
  type: "object",
7974
8332
  properties: {
7975
8333
  uid: {
7976
8334
  type: "number",
7977
- description: "CDP backendDOMNodeId of the field, obtained from take_snapshot. Provide exactly one of uid or selector."
8335
+ description: "Element backendDOMNodeId."
7978
8336
  },
7979
8337
  selector: {
7980
8338
  type: "string",
7981
- description: "CSS selector for the field (resolved via document.querySelector). Provide exactly one of uid or selector."
8339
+ description: "CSS selector."
7982
8340
  },
7983
8341
  value: {
7984
8342
  type: "string",
7985
- description: "The text value to set; the field is cleared first so this overwrites existing content."
8343
+ description: "Text value to set; the field is cleared first, overwriting existing content."
7986
8344
  }
7987
8345
  },
7988
8346
  required: [
@@ -8000,29 +8358,29 @@ var MANIFEST = [
8000
8358
  },
8001
8359
  {
8002
8360
  name: "type_text",
8003
- description: "Focuses an element and appends text via Input.insertText without clearing first — closest primitive to typing, but still atomic (not per-character keystrokes). Give exactly one of uid or selector.",
8361
+ description: "Append text to an element; input is atomic and does not clear existing content.",
8004
8362
  inputSchema: {
8005
8363
  type: "object",
8006
8364
  properties: {
8007
8365
  lease: {
8008
8366
  type: "string",
8009
- description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
8367
+ description: "Lease token; omit for a tab this process already holds. See server instructions."
8010
8368
  },
8011
8369
  target: {
8012
8370
  type: "string",
8013
- description: "Page selector: 'active' (default), 'index:N', 'url:<substr>', 'title:<substr>', 'label:<name>', or a 32-hex '<targetId>'. Grammar: see server instructions."
8371
+ description: "Page selector (index:/url:/title:/label:/targetId, default active); grammar in server instructions."
8014
8372
  },
8015
8373
  uid: {
8016
8374
  type: "number",
8017
- description: "CDP backendDOMNodeId of the element to type into, from take_snapshot. Exactly one of uid or selector."
8375
+ description: "Element backendDOMNodeId."
8018
8376
  },
8019
8377
  selector: {
8020
8378
  type: "string",
8021
- description: "CSS selector for the element to type into (document.querySelector). Exactly one of uid or selector."
8379
+ description: "CSS selector."
8022
8380
  },
8023
8381
  text: {
8024
8382
  type: "string",
8025
- description: "Text to insert; appended to existing content rather than overwriting it."
8383
+ description: "Text substring."
8026
8384
  }
8027
8385
  },
8028
8386
  required: [
@@ -8033,25 +8391,25 @@ var MANIFEST = [
8033
8391
  },
8034
8392
  {
8035
8393
  name: "press_key",
8036
- description: "Dispatches a keyDown/keyUp pair for one key with optional modifiers. 'key' is a named key (Enter, Tab, Escape, ArrowDown, Backspace, etc.) or a single printable character — the named-key table is curated, with no F-keys, numpad, or IME support.",
8394
+ description: "Press one named key or printable character with modifiers; no F-keys or IME.",
8037
8395
  inputSchema: {
8038
8396
  type: "object",
8039
8397
  properties: {
8040
8398
  lease: {
8041
8399
  type: "string",
8042
- description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
8400
+ description: "Lease token; omit for a tab this process already holds. See server instructions."
8043
8401
  },
8044
8402
  target: {
8045
8403
  type: "string",
8046
- description: "Page selector: 'active' (default), 'index:N', 'url:<substr>', 'title:<substr>', 'label:<name>', or a 32-hex '<targetId>'. Grammar: see server instructions."
8404
+ description: "Page selector (index:/url:/title:/label:/targetId, default active); grammar in server instructions."
8047
8405
  },
8048
8406
  key: {
8049
8407
  type: "string",
8050
- description: "Key to press: a named key (Enter, Tab, Escape, ArrowDown, Backspace, ...) or a single printable character."
8408
+ description: "Named key or printable character; e.g. Enter|Tab|Escape|ArrowDown|Backspace."
8051
8409
  },
8052
8410
  modifiers: {
8053
8411
  type: "array",
8054
- description: "Modifier names held during the press: 'Control'/'Ctrl', 'Shift', 'Alt', 'Meta'/'Cmd'.",
8412
+ description: "Modifier keys: Control|Ctrl|Shift|Alt|Meta|Cmd.",
8055
8413
  items: {
8056
8414
  type: "string"
8057
8415
  }
@@ -8065,32 +8423,32 @@ var MANIFEST = [
8065
8423
  },
8066
8424
  {
8067
8425
  name: "upload_file",
8068
- description: "Attaches one or more files to an <input type=file> element via DOM.setFileInputFiles. Give exactly one of uid or selector for the input; 'files' is an array of absolute path(s) — wrap a single path in an array too.",
8426
+ description: "Attach absolute file paths to a file input; provide an array.",
8069
8427
  inputSchema: {
8070
8428
  type: "object",
8071
8429
  properties: {
8072
8430
  lease: {
8073
8431
  type: "string",
8074
- description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
8432
+ description: "Lease token; omit for a tab this process already holds. See server instructions."
8075
8433
  },
8076
8434
  target: {
8077
8435
  type: "string",
8078
- description: "Page selector: 'active' (default), 'index:N', 'url:<substr>', 'title:<substr>', 'label:<name>', or a 32-hex '<targetId>'. Grammar: see server instructions."
8436
+ description: "Page selector (index:/url:/title:/label:/targetId, default active); grammar in server instructions."
8079
8437
  },
8080
8438
  uid: {
8081
8439
  type: "number",
8082
- description: "CDP backendDOMNodeId of the file input element, from take_snapshot. Exactly one of uid or selector."
8440
+ description: "Element backendDOMNodeId."
8083
8441
  },
8084
8442
  selector: {
8085
8443
  type: "string",
8086
- description: "CSS selector for the file input element (document.querySelector). Exactly one of uid or selector."
8444
+ description: "CSS selector."
8087
8445
  },
8088
8446
  files: {
8089
8447
  type: "array",
8090
8448
  items: {
8091
8449
  type: "string"
8092
8450
  },
8093
- description: 'Absolute path(s) to attach to the <input type=file>, as an array — wrap a single path too, e.g. ["/tmp/a.pdf"]. Array-only because some model APIs (Google Gemini function calling) reject union-typed parameters.'
8451
+ description: "Absolute file paths."
8094
8452
  }
8095
8453
  },
8096
8454
  required: [
@@ -8101,17 +8459,17 @@ var MANIFEST = [
8101
8459
  },
8102
8460
  {
8103
8461
  name: "take_screenshot",
8104
- description: "Captures the viewport (default), the full scrollable page (fullPage), or one element (uid or selector, mutually exclusive) via CDP Page.captureScreenshot. Past 16384 device px per side Chrome can hang rather than refuse, so size is checked first via Page.getLayoutMetrics; an oversized capture is auto-tiled into vertical bands stitched losslessly into one PNG (see tile) instead of wedging the tab. Writes PNG/JPEG to /tmp/cdp-toolkit (override savePath, dir auto-created); returns {path,bytes,format,target,scale} plus width/height measured from the encoded bytes (omitted if undecodable). renderSize/renderRestored(+renderRestoreError) accompany a renderWidth/renderHeight capture, tiled/bands a banded one. returnBase64 adds raw bytes to the result but is refused on a banded capture. quality applies to jpeg only.",
8462
+ description: "Capture a viewport, full page, or element to PNG/JPEG; oversized captures may be tiled.",
8105
8463
  inputSchema: {
8106
8464
  type: "object",
8107
8465
  properties: {
8108
8466
  lease: {
8109
8467
  type: "string",
8110
- description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
8468
+ description: "Lease token; omit for a tab this process already holds. See server instructions."
8111
8469
  },
8112
8470
  target: {
8113
8471
  type: "string",
8114
- description: "Page selector: 'active' (default), 'index:N', 'url:<substr>', 'title:<substr>', 'label:<name>', or a 32-hex '<targetId>'. Grammar: see server instructions."
8472
+ description: "Page selector (index:/url:/title:/label:/targetId, default active); grammar in server instructions."
8115
8473
  },
8116
8474
  format: {
8117
8475
  type: "string",
@@ -8119,47 +8477,47 @@ var MANIFEST = [
8119
8477
  "png",
8120
8478
  "jpeg"
8121
8479
  ],
8122
- description: "Image format, default png. quality applies only to jpeg."
8480
+ description: "Image/frame format."
8123
8481
  },
8124
8482
  quality: {
8125
8483
  type: "number",
8126
- description: "JPEG quality 0-100, default 80; ignored for png."
8484
+ description: "JPEG quality 0-100."
8127
8485
  },
8128
8486
  fullPage: {
8129
8487
  type: "boolean",
8130
- description: "Capture the full scrollable height (via Page.getLayoutMetrics), not just the viewport."
8488
+ description: "Capture full scrollable page."
8131
8489
  },
8132
8490
  scale: {
8133
8491
  type: "number",
8134
- description: "Output-pixel multiplier for this capture only (0<scale<=8, default 1): output px = ceil(css px x scale x devicePixelRatio); the page is never told anything changed. Chrome only (screenshot.scale) — Firefox BiDi has no scale param, use emulate{deviceScaleFactor} then scale:1. A projection over 16384 device px/side is refused up front, naming the largest scale that fits."
8492
+ description: "Output scale (0<scale<=8)."
8135
8493
  },
8136
8494
  renderWidth: {
8137
8495
  type: "number",
8138
- description: "Emulates this viewport width (CSS px, 1-16384) for one capture, then restores it. Required with renderHeight. Unlike scale, this changes what the page believes (media queries re-flow) — e.g. capture a responsive page at 1920x1080 from a differently-sized tab. Restore only works for an override this toolkit itself set; check result.renderRestored (false = still emulated, fix via emulate). Works on both backends (capability screenshot.renderSize)."
8496
+ description: "Rendered viewport width."
8139
8497
  },
8140
8498
  renderHeight: {
8141
8499
  type: "number",
8142
- description: "Emulates this viewport height (CSS px, 1-16384) for one capture. Required with renderWidth — a viewport is two numbers. A narrow renderWidth often reflows pages TALLER, which is the usual way a render-size capture trips the 16384px encode limit."
8500
+ description: "Rendered viewport height."
8143
8501
  },
8144
8502
  tile: {
8145
8503
  type: "boolean",
8146
- description: "Whether to allow vertical-band stitching: omitted=AUTO (band only if a single shot wouldn't fit), false=never band (refuse instead), true=always band (Chrome-only capability). Banded output is PNG-only, can't returnBase64, and only bands on HEIGHT (oversized WIDTH is refused, not split). Bands are a faithful crop, but lazy/virtualized content the real viewport never scrolled to renders blank."
8504
+ description: "Vertical-band stitching."
8147
8505
  },
8148
8506
  uid: {
8149
8507
  type: "number",
8150
- description: "Element to clip to: a backendDOMNodeId from take_snapshot. Mutually exclusive with selector."
8508
+ description: "Element backendDOMNodeId."
8151
8509
  },
8152
8510
  selector: {
8153
8511
  type: "string",
8154
- description: "CSS selector to clip the capture to. Mutually exclusive with uid."
8512
+ description: "CSS selector."
8155
8513
  },
8156
8514
  savePath: {
8157
8515
  type: "string",
8158
- description: "Override the output path (default /tmp/cdp-toolkit/screenshot-<id>-<stamp>.<ext>, stamped at capture START). Directory is created from this path's dirname."
8516
+ description: "Output file path."
8159
8517
  },
8160
8518
  returnBase64: {
8161
8519
  type: "boolean",
8162
- description: "Also return raw base64 bytes in the result. Refused on a banded capture (see tile) — a stitched image can run to hundreds of MB; read the file at the returned path instead."
8520
+ description: "Return base64 bytes."
8163
8521
  }
8164
8522
  },
8165
8523
  required: [],
@@ -8168,49 +8526,49 @@ var MANIFEST = [
8168
8526
  },
8169
8527
  {
8170
8528
  name: "emulate",
8171
- description: "Applies any subset of Chrome emulation overrides in one call: device metrics (width+height together, +deviceScaleFactor/mobile), userAgent, cpuThrottlingRate(>=1), media type+features, networkConditions. Device-metrics overrides PERSIST on the target until cleared or the renderer navigates/reloads; every other override is session-scoped and resets when this call's connection closes. clearOverrides:true resets everything (ignoring other fields) and, as of 1.9.2, genuinely clears a device-metrics override set by a DIFFERENT process — Chrome's clearDeviceMetricsOverride is normally a no-op unless the clearing connection also set the override, so the driver re-asserts the tab's current size first, then clears, throwing rather than reporting cleared:true if it can't verify.",
8529
+ description: "Apply browser emulation overrides; width and height must be paired.",
8172
8530
  inputSchema: {
8173
8531
  type: "object",
8174
8532
  properties: {
8175
8533
  lease: {
8176
8534
  type: "string",
8177
- description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
8535
+ description: "Lease token; omit for a tab this process already holds. See server instructions."
8178
8536
  },
8179
8537
  target: {
8180
8538
  type: "string",
8181
- description: "Page selector: 'active' (default), 'index:N', 'url:<substr>', 'title:<substr>', 'label:<name>', or a 32-hex '<targetId>'. Grammar: see server instructions."
8539
+ description: "Page selector (index:/url:/title:/label:/targetId, default active); grammar in server instructions."
8182
8540
  },
8183
8541
  width: {
8184
8542
  type: "number",
8185
- description: "Device-metrics viewport width in CSS px. Must be paired with height."
8543
+ description: "Viewport width; pair with height."
8186
8544
  },
8187
8545
  height: {
8188
8546
  type: "number",
8189
- description: "Device-metrics viewport height in CSS px. Must be paired with width."
8547
+ description: "Viewport height; pair with width."
8190
8548
  },
8191
8549
  deviceScaleFactor: {
8192
8550
  type: "number",
8193
- description: "Device pixel ratio for the device-metrics override; 0 = platform default."
8551
+ description: "Device pixel ratio."
8194
8552
  },
8195
8553
  mobile: {
8196
8554
  type: "boolean",
8197
- description: "Emulate a mobile device (viewport meta, scrollbars, etc.) for the device-metrics override."
8555
+ description: "Mobile emulation."
8198
8556
  },
8199
8557
  userAgent: {
8200
8558
  type: "string",
8201
- description: "User-Agent string override (Emulation.setUserAgentOverride)."
8559
+ description: "User-Agent override."
8202
8560
  },
8203
8561
  cpuThrottlingRate: {
8204
8562
  type: "number",
8205
- description: "CPU throttling multiplier: 1=none, 2=2x slower, etc. Must be >=1."
8563
+ description: "CPU throttling rate (>=1)."
8206
8564
  },
8207
8565
  media: {
8208
8566
  type: "string",
8209
- description: "Emulated CSS media type: 'screen' | 'print' | '' (clear)."
8567
+ description: "CSS media: screen|print|blank."
8210
8568
  },
8211
8569
  mediaFeatures: {
8212
8570
  type: "array",
8213
- description: "Emulated media features, e.g. [{name:'prefers-color-scheme', value:'dark'}].",
8571
+ description: "CSS media feature overrides.",
8214
8572
  items: {
8215
8573
  type: "object",
8216
8574
  properties: {
@@ -8220,7 +8578,7 @@ var MANIFEST = [
8220
8578
  },
8221
8579
  value: {
8222
8580
  type: "string",
8223
- description: "Media feature value, e.g. 'dark'."
8581
+ description: "Feature value, e.g. 'dark'."
8224
8582
  }
8225
8583
  },
8226
8584
  required: [
@@ -8232,34 +8590,34 @@ var MANIFEST = [
8232
8590
  },
8233
8591
  networkConditions: {
8234
8592
  type: "object",
8235
- description: "Network overrides applied via Network.emulateNetworkConditions.",
8593
+ description: "Network condition overrides.",
8236
8594
  properties: {
8237
8595
  offline: {
8238
8596
  type: "boolean",
8239
- description: "True to simulate offline."
8597
+ description: "Simulate offline."
8240
8598
  },
8241
8599
  latency: {
8242
8600
  type: "number",
8243
- description: "Additional round-trip latency in milliseconds."
8601
+ description: "Network latency in milliseconds."
8244
8602
  },
8245
8603
  downloadThroughput: {
8246
8604
  type: "number",
8247
- description: "Max download throughput in bytes/sec (-1 = no limit)."
8605
+ description: "Download throughput; -1 unlimited."
8248
8606
  },
8249
8607
  uploadThroughput: {
8250
8608
  type: "number",
8251
- description: "Max upload throughput in bytes/sec (-1 = no limit)."
8609
+ description: "Upload throughput; -1 unlimited."
8252
8610
  },
8253
8611
  connectionType: {
8254
8612
  type: "string",
8255
- description: "Connection type: 'none' | 'cellular2g' | 'cellular3g' | 'cellular4g' | 'bluetooth' | 'ethernet' | 'wifi' | 'wimax' | 'other'."
8613
+ description: "Connection: none|cellular2g|cellular3g|cellular4g|bluetooth|ethernet|wifi|wimax|other."
8256
8614
  }
8257
8615
  },
8258
8616
  additionalProperties: false
8259
8617
  },
8260
8618
  clearOverrides: {
8261
8619
  type: "boolean",
8262
- description: "Reset every override (metrics, UA, CPU, media, network) to browser default; ignores all other fields when true."
8620
+ description: "Clear all emulation overrides."
8263
8621
  }
8264
8622
  },
8265
8623
  required: [],
@@ -8268,33 +8626,33 @@ var MANIFEST = [
8268
8626
  },
8269
8627
  {
8270
8628
  name: "resize_page",
8271
- description: "Sets the page's device-metrics width/height (the narrow case of emulate), optionally deviceScaleFactor/mobile, then verifies by reading back window.innerWidth/innerHeight after applying. Requires positive width and height. The override PERSISTS on the target until cleared (emulate{clearOverrides:true}) or the renderer navigates/reloads.",
8629
+ description: "Set and verify page viewport metrics; width and height are required.",
8272
8630
  inputSchema: {
8273
8631
  type: "object",
8274
8632
  properties: {
8275
8633
  lease: {
8276
8634
  type: "string",
8277
- description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
8635
+ description: "Lease token; omit for a tab this process already holds. See server instructions."
8278
8636
  },
8279
8637
  target: {
8280
8638
  type: "string",
8281
- description: "Page selector: 'active' (default), 'index:N', 'url:<substr>', 'title:<substr>', 'label:<name>', or a 32-hex '<targetId>'. Grammar: see server instructions."
8639
+ description: "Page selector (index:/url:/title:/label:/targetId, default active); grammar in server instructions."
8282
8640
  },
8283
8641
  width: {
8284
8642
  type: "number",
8285
- description: "Viewport width in CSS px (required, must be positive)."
8643
+ description: "Viewport width; pair with height."
8286
8644
  },
8287
8645
  height: {
8288
8646
  type: "number",
8289
- description: "Viewport height in CSS px (required, must be positive)."
8647
+ description: "Viewport height; pair with width."
8290
8648
  },
8291
8649
  deviceScaleFactor: {
8292
8650
  type: "number",
8293
- description: "Device pixel ratio; 0 = platform default."
8651
+ description: "Device pixel ratio."
8294
8652
  },
8295
8653
  mobile: {
8296
8654
  type: "boolean",
8297
- description: "Emulate a mobile device for the device-metrics override."
8655
+ description: "Mobile emulation."
8298
8656
  }
8299
8657
  },
8300
8658
  required: [
@@ -8306,33 +8664,33 @@ var MANIFEST = [
8306
8664
  },
8307
8665
  {
8308
8666
  name: "handle_dialog",
8309
- description: "Wait for the next JavaScript dialog (alert/confirm/prompt/beforeunload) on a page and respond via Page.handleJavaScriptDialog, accepting or dismissing it. Default mode resolves with the first handled dialog or throws on timeout; set autoMs to handle every dialog opening during a fixed window and resolve with the list. Trigger the dialog out-of-band (e.g. click a button) — a blocking dialog freezes the renderer, so it cannot be triggered inline.",
8667
+ description: "Handle the next JavaScript dialog; autoMs switches to timed multi-dialog mode.",
8310
8668
  inputSchema: {
8311
8669
  type: "object",
8312
8670
  properties: {
8313
8671
  lease: {
8314
8672
  type: "string",
8315
- description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
8673
+ description: "Lease token; omit for a tab this process already holds. See server instructions."
8316
8674
  },
8317
8675
  target: {
8318
8676
  type: "string",
8319
- description: "Page selector: 'active' (default), 'index:N', 'url:<substr>', 'title:<substr>', 'label:<name>', or a 32-hex '<targetId>'. Grammar: see server instructions."
8677
+ description: "Page selector (index:/url:/title:/label:/targetId, default active); grammar in server instructions."
8320
8678
  },
8321
8679
  accept: {
8322
8680
  type: "boolean",
8323
- description: "Whether to accept (OK) or dismiss (Cancel) the dialog."
8681
+ description: "Accept or dismiss dialog."
8324
8682
  },
8325
8683
  promptText: {
8326
8684
  type: "string",
8327
- description: "Text to enter for a prompt() dialog when accepting."
8685
+ description: "Prompt response text."
8328
8686
  },
8329
8687
  timeoutMs: {
8330
8688
  type: "number",
8331
- description: "How long to wait for the next dialog, in milliseconds (default 15000)."
8689
+ description: "Timeout in milliseconds."
8332
8690
  },
8333
8691
  autoMs: {
8334
8692
  type: "number",
8335
- description: "Auto-handle mode: keep handling every dialog that opens for this many milliseconds, then resolve with the list of handled dialogs (an empty list is valid; never throws on 'no dialog')."
8693
+ description: "Multi-dialog window in milliseconds."
8336
8694
  }
8337
8695
  },
8338
8696
  required: [
@@ -8343,29 +8701,29 @@ var MANIFEST = [
8343
8701
  },
8344
8702
  {
8345
8703
  name: "list_console_messages",
8346
- description: "Read console output (logs, warnings, exceptions) captured for the target. Reads the existing shared buffer by default (empty if no capture ran); reload:true reloads and records a fresh capture (console+network together, so a network reload never wipes console history). WORKER capture (Chrome, Capability 'worker.targets'): a worker: target records an MV3 background service worker -- its console.log arrives as Runtime.consoleAPICalled, its outbound requests from CDP's Network domain on the worker's own session -- so the two tricks callers try first fail for plain JS reasons, not worker ones: a value bound at module top level is not visible from evaluate_script's global scope, and assigning self.fetch cannot rebind a fetch the module already captured. A worker has no reload: reload:true means 'listen for durationMs while it runs', so trigger its work (tool call, message, alarm) during the window; a worker woken by the call already ran its top-level code, so a startup message can be missed. SIDE EFFECT: the CDP session KEEPS THE WORKER ALIVE for the capture (an idle MV3 worker is evicted in seconds); Chrome resumes evicting after.",
8704
+ description: "Read or recapture console messages for a page or worker; worker capture is Chrome-only.",
8347
8705
  inputSchema: {
8348
8706
  type: "object",
8349
8707
  properties: {
8350
8708
  lease: {
8351
8709
  type: "string",
8352
- description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
8710
+ description: "Lease token; omit for a tab this process already holds. See server instructions."
8353
8711
  },
8354
8712
  target: {
8355
8713
  type: "string",
8356
- description: "Page selector: 'active' (default), 'index:N', 'url:<substr>', 'title:<substr>', 'label:<name>', '<targetId>'; plus 'worker:<substring>' to reach a service/shared worker (e.g. an MV3 extension background worker). The worker: arm is CHROME-ONLY (Capability 'worker.targets'). Grammar: see server instructions."
8714
+ description: "Page selector (index:/url:/title:/label:/targetId, default active); grammar in server instructions."
8357
8715
  },
8358
8716
  reload: {
8359
8717
  type: "boolean",
8360
- description: "Record fresh by reloading the page and capturing a window. Default false (read existing buffer). With a worker: target nothing reloads -- records for durationMs while the worker runs and keeps it alive until the capture stops."
8718
+ description: "Reload before capture."
8361
8719
  },
8362
8720
  durationMs: {
8363
8721
  type: "number",
8364
- description: "Capture window for reload mode, in ms. Default 2500."
8722
+ description: "Capture duration in milliseconds."
8365
8723
  },
8366
8724
  wake: {
8367
8725
  type: "boolean",
8368
- description: "Worker targets only, when starting a capture. Default true: an idle-evicted MV3 service worker exists in no target listing, so it is started first (ServiceWorker.startWorker) and re-confirmed; false fails fast. Rejected on a page target, a bare target id, or a read-only call. See server instructions."
8726
+ description: "Wake worker before capture."
8369
8727
  }
8370
8728
  },
8371
8729
  required: [],
@@ -8374,21 +8732,21 @@ var MANIFEST = [
8374
8732
  },
8375
8733
  {
8376
8734
  name: "get_console_message",
8377
- description: "Return one console entry by zero-based index from the target's existing console buffer. Throws if the index is out of range; run list_console_messages (optionally reload:true) first to populate the buffer.",
8735
+ description: "Return one console entry by zero-based index from the existing buffer.",
8378
8736
  inputSchema: {
8379
8737
  type: "object",
8380
8738
  properties: {
8381
8739
  lease: {
8382
8740
  type: "string",
8383
- description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
8741
+ description: "Lease token; omit for a tab this process already holds. See server instructions."
8384
8742
  },
8385
8743
  target: {
8386
8744
  type: "string",
8387
- description: "Page selector: 'active' (default), 'index:N', 'url:<substr>', 'title:<substr>', 'label:<name>', or a 32-hex '<targetId>'. Grammar: see server instructions."
8745
+ description: "Page selector (index:/url:/title:/label:/targetId, default active); grammar in server instructions."
8388
8746
  },
8389
8747
  index: {
8390
8748
  type: "number",
8391
- description: "Zero-based index into the parsed console entries. Default 0."
8749
+ description: "Zero-based entry index."
8392
8750
  }
8393
8751
  },
8394
8752
  required: [],
@@ -8397,33 +8755,33 @@ var MANIFEST = [
8397
8755
  },
8398
8756
  {
8399
8757
  name: "list_network_requests",
8400
- description: "Return correlated network rows (one per requestId; status/headers/state) for the target. Reads the existing buffer by default; reload:true records a fresh both-domains capture window. filterUrl keeps only URLs containing a substring. WORKER capture (Chrome, Capability 'worker.targets'): a worker: target records an MV3 background service worker's outbound requests from CDP's Network domain on the worker's own session -- what the code ACTUALLY SENT -- so the two tricks callers try first fail for plain JS reasons, not worker ones: a value bound at module top level is not visible from evaluate_script's global scope, and assigning self.fetch cannot rebind a fetch the module already captured. A worker has no reload: reload:true means 'listen for durationMs while it runs', so trigger its work (tool call, message, alarm) during the window; a worker woken by the call already ran its top-level code, so a startup request can be missed. SIDE EFFECT: the CDP session KEEPS THE WORKER ALIVE for the capture (an idle MV3 worker is evicted in seconds); Chrome resumes evicting after.",
8758
+ description: "Read or recapture correlated network requests; worker capture is Chrome-only.",
8401
8759
  inputSchema: {
8402
8760
  type: "object",
8403
8761
  properties: {
8404
8762
  lease: {
8405
8763
  type: "string",
8406
- description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
8764
+ description: "Lease token; omit for a tab this process already holds. See server instructions."
8407
8765
  },
8408
8766
  target: {
8409
8767
  type: "string",
8410
- description: "Page selector: 'active' (default), 'index:N', 'url:<substr>', 'title:<substr>', 'label:<name>', '<targetId>'; plus 'worker:<substring>' to reach a service/shared worker (e.g. an MV3 extension background worker). The worker: arm is CHROME-ONLY (Capability 'worker.targets'). Grammar: see server instructions."
8768
+ description: "Page selector (index:/url:/title:/label:/targetId, default active); grammar in server instructions."
8411
8769
  },
8412
8770
  reload: {
8413
8771
  type: "boolean",
8414
- description: "Record fresh by reloading the page and capturing a window. Default false (read existing buffer). With a worker: target nothing reloads -- records for durationMs while the worker runs and keeps it alive until the capture stops."
8772
+ description: "Reload before capture."
8415
8773
  },
8416
8774
  durationMs: {
8417
8775
  type: "number",
8418
- description: "Capture window for reload mode, in ms. Default 2500."
8776
+ description: "Capture duration in milliseconds."
8419
8777
  },
8420
8778
  filterUrl: {
8421
8779
  type: "string",
8422
- description: "Return only requests whose URL contains this substring."
8780
+ description: "URL substring filter."
8423
8781
  },
8424
8782
  wake: {
8425
8783
  type: "boolean",
8426
- description: "Worker targets only, when starting a capture. Default true: an idle-evicted MV3 service worker exists in no target listing, so it is started first (ServiceWorker.startWorker) and re-confirmed; false fails fast. Rejected on a page target, a bare target id, or a read-only call. See server instructions."
8784
+ description: "Wake worker before capture."
8427
8785
  }
8428
8786
  },
8429
8787
  required: [],
@@ -8432,37 +8790,37 @@ var MANIFEST = [
8432
8790
  },
8433
8791
  {
8434
8792
  name: "get_network_request",
8435
- description: "Return one network request (matched by exact requestId, else url substring) with status/headers. Requires requestId or url (throws otherwise). includeBody:true drives a fresh capture (page reload, or worker listen window) matched by url ONLY -- a fresh capture re-mints requestIds, so a carried-over requestId returns metadata plus bodyUnavailableReason. WORKER capture (Chrome, Capability 'worker.targets'): a worker: target records an MV3 background service worker's outbound requests from CDP's Network domain on the worker's own session -- what the code ACTUALLY SENT -- so the two tricks callers try first fail for plain JS reasons, not worker ones: a value bound at module top level is not visible from evaluate_script's global scope, and assigning self.fetch cannot rebind a fetch the module already captured. A worker has no reload: reload means 'listen for durationMs while it runs', so trigger its work (tool call, message, alarm) during the window; a worker woken by the call already ran its top-level code, so a startup request can be missed. SIDE EFFECT: the CDP session KEEPS THE WORKER ALIVE for the capture (an idle MV3 worker is evicted in seconds); Chrome resumes evicting after.",
8793
+ description: "Return a network request by ID or URL; body capture requires URL.",
8436
8794
  inputSchema: {
8437
8795
  type: "object",
8438
8796
  properties: {
8439
8797
  lease: {
8440
8798
  type: "string",
8441
- description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
8799
+ description: "Lease token; omit for a tab this process already holds. See server instructions."
8442
8800
  },
8443
8801
  target: {
8444
8802
  type: "string",
8445
- description: "Page selector: 'active' (default), 'index:N', 'url:<substr>', 'title:<substr>', 'label:<name>', '<targetId>'; plus 'worker:<substring>' to reach a service/shared worker (e.g. an MV3 extension background worker). The worker: arm is CHROME-ONLY (Capability 'worker.targets'). Grammar: see server instructions."
8803
+ description: "Page selector (index:/url:/title:/label:/targetId, default active); grammar in server instructions."
8446
8804
  },
8447
8805
  requestId: {
8448
8806
  type: "string",
8449
- description: "Match by exact requestId (metadata only; cannot fetch a body, since a reload re-mints requestIds)."
8807
+ description: "Exact request ID."
8450
8808
  },
8451
8809
  url: {
8452
8810
  type: "string",
8453
- description: "Match by URL substring (first match). Required for body fetch (includeBody), since url is stable across reload."
8811
+ description: "URL value."
8454
8812
  },
8455
8813
  includeBody: {
8456
8814
  type: "boolean",
8457
- description: "Also fetch the response body; drives a fresh capture and requires the url selector."
8815
+ description: "Include response body."
8458
8816
  },
8459
8817
  durationMs: {
8460
8818
  type: "number",
8461
- description: "Capture window for the body-fetch reload, in ms. Default 2500."
8819
+ description: "Capture duration in milliseconds."
8462
8820
  },
8463
8821
  wake: {
8464
8822
  type: "boolean",
8465
- description: "Worker targets only, when starting a capture. Default true: an idle-evicted MV3 service worker exists in no target listing, so it is started first (ServiceWorker.startWorker) and re-confirmed; false fails fast. Rejected on a page target, a bare target id, or a read-only call. See server instructions."
8823
+ description: "Wake worker before capture."
8466
8824
  }
8467
8825
  },
8468
8826
  required: [],
@@ -8471,24 +8829,24 @@ var MANIFEST = [
8471
8829
  },
8472
8830
  {
8473
8831
  name: "performance_start_trace",
8474
- description: "Starts a CDP trace recording on the target and holds the recording connection in-process; you MUST call performance_stop_trace from the SAME PROCESS to end it, or use performance_trace instead for a robust cross-call trace. Throws if a trace is already in progress for this target.",
8832
+ description: "Start an in-process trace; stop it from the same process.",
8475
8833
  inputSchema: {
8476
8834
  type: "object",
8477
8835
  properties: {
8478
8836
  lease: {
8479
8837
  type: "string",
8480
- description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
8838
+ description: "Lease token; omit for a tab this process already holds. See server instructions."
8481
8839
  },
8482
8840
  target: {
8483
8841
  type: "string",
8484
- description: "Page selector: 'active' (default), 'index:N', 'url:<substr>', 'title:<substr>', 'label:<name>', or a 32-hex '<targetId>'. Grammar: see server instructions."
8842
+ description: "Page selector (index:/url:/title:/label:/targetId, default active); grammar in server instructions."
8485
8843
  },
8486
8844
  categories: {
8487
8845
  type: "array",
8488
8846
  items: {
8489
8847
  type: "string"
8490
8848
  },
8491
- description: "Trace categories to record. Defaults to timeline + user-timing + loading + disabled-by-default timeline tracks carrying LCP/LayoutShift/RunTask."
8849
+ description: "Trace categories."
8492
8850
  }
8493
8851
  },
8494
8852
  required: [],
@@ -8497,17 +8855,17 @@ var MANIFEST = [
8497
8855
  },
8498
8856
  {
8499
8857
  name: "performance_stop_trace",
8500
- description: "Stops the in-process trace started by performance_start_trace and writes it as JSON under /tmp/cdp-toolkit, returning {path,bytes,events,metrics}. Throws if no live trace exists in THIS process (e.g. start ran in a different process) — use performance_trace instead. The target arg is accepted only for API symmetry; at most one trace is ever live per process.",
8858
+ description: "Stop the in-process trace and write JSON; a live trace is required.",
8501
8859
  inputSchema: {
8502
8860
  type: "object",
8503
8861
  properties: {
8504
8862
  lease: {
8505
8863
  type: "string",
8506
- description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
8864
+ description: "Lease token; omit for a tab this process already holds. See server instructions."
8507
8865
  },
8508
8866
  target: {
8509
8867
  type: "string",
8510
- description: "Page selector: 'active' (default), 'index:N', 'url:<substr>', 'title:<substr>', 'label:<name>', or a 32-hex '<targetId>'. Grammar: see server instructions."
8868
+ description: "Page selector (index:/url:/title:/label:/targetId, default active); grammar in server instructions."
8511
8869
  }
8512
8870
  },
8513
8871
  required: [],
@@ -8516,17 +8874,17 @@ var MANIFEST = [
8516
8874
  },
8517
8875
  {
8518
8876
  name: "performance_analyze_insight",
8519
- description: "Reads a trace JSON file (from performance_trace/performance_stop_trace) at tracePath and returns headline metrics: FCP/LCP/CLS/TBT, long tasks, layout shifts. Requires an EXPLICIT tracePath — there is no implicit 'latest trace'. Numbers approximate DevTools's insight analyzer (no main-thread attribution or frame-scoped LCP).",
8877
+ description: "Analyze a trace JSON file for key performance metrics; tracePath is required.",
8520
8878
  inputSchema: {
8521
8879
  type: "object",
8522
8880
  properties: {
8523
8881
  lease: {
8524
8882
  type: "string",
8525
- description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
8883
+ description: "Lease token; omit for a tab this process already holds. See server instructions."
8526
8884
  },
8527
8885
  tracePath: {
8528
8886
  type: "string",
8529
- description: "Path to a trace JSON file (bare array or {traceEvents:[...]}) written by performance_trace or performance_stop_trace. Required."
8887
+ description: "Trace JSON file path."
8530
8888
  }
8531
8889
  },
8532
8890
  required: [],
@@ -8535,36 +8893,36 @@ var MANIFEST = [
8535
8893
  },
8536
8894
  {
8537
8895
  name: "performance_trace",
8538
- description: "RECOMMENDED entry point: one-shot trace that starts tracing, optionally reloads or navigates the page, waits durationMs (default 3000ms), stops, and writes JSON under /tmp/cdp-toolkit — returns {path,bytes,events,metrics,target}. Holds one connection open for the whole window, so unlike start/stop it is NOT limited to a single process.",
8896
+ description: "Run a one-shot trace with optional navigation and write JSON; durationMs controls capture time.",
8539
8897
  inputSchema: {
8540
8898
  type: "object",
8541
8899
  properties: {
8542
8900
  lease: {
8543
8901
  type: "string",
8544
- description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
8902
+ description: "Lease token; omit for a tab this process already holds. See server instructions."
8545
8903
  },
8546
8904
  target: {
8547
8905
  type: "string",
8548
- description: "Page selector: 'active' (default), 'index:N', 'url:<substr>', 'title:<substr>', 'label:<name>', or a 32-hex '<targetId>'. Grammar: see server instructions."
8906
+ description: "Page selector (index:/url:/title:/label:/targetId, default active); grammar in server instructions."
8549
8907
  },
8550
8908
  durationMs: {
8551
8909
  type: "number",
8552
- description: "How long to record after the optional reload/navigate, in ms. Default 3000."
8910
+ description: "Capture duration in milliseconds."
8553
8911
  },
8554
8912
  reload: {
8555
8913
  type: "boolean",
8556
- description: "Reload the page after starting the trace, to capture full navigation timing."
8914
+ description: "Reload before capture."
8557
8915
  },
8558
8916
  navigateTo: {
8559
8917
  type: "string",
8560
- description: "Navigate to this URL after starting the trace (alternative to reload)."
8918
+ description: "Navigation URL."
8561
8919
  },
8562
8920
  categories: {
8563
8921
  type: "array",
8564
8922
  items: {
8565
8923
  type: "string"
8566
8924
  },
8567
- description: "Trace categories to record. Defaults to timeline + user-timing + loading + disabled-by-default timeline tracks carrying LCP/LayoutShift/RunTask."
8925
+ description: "Trace categories."
8568
8926
  }
8569
8927
  },
8570
8928
  required: [],
@@ -8573,17 +8931,17 @@ var MANIFEST = [
8573
8931
  },
8574
8932
  {
8575
8933
  name: "start_screen_recording",
8576
- description: "Starts recording the target tab to video via CDP Page.startScreencast, holding one persistent connection and spooling every frame to disk — MUST be paired with stop_screen_recording WITHIN THE SAME PROCESS (frames are connection-scoped and cannot be reattached from another process, so this only works under the MCP server, not the one-process-per-call CLI). Chrome only: WebDriver BiDi has no screencast primitive, so both tools are absent under --browser firefox. Frames arrive ON REPAINT not on a clock (variable rate); each is timestamped into a ledger that stop_screen_recording turns into per-frame display durations, so a still page holds one long frame instead of racing. ffmpeg is probed here (not at stop) so a missing encoder fails before frames are captured and thrown away. Throws if the target already has a recording in progress; different targets record concurrently.",
8934
+ description: "Start a tab screen recording; stop it from the same process.",
8577
8935
  inputSchema: {
8578
8936
  type: "object",
8579
8937
  properties: {
8580
8938
  lease: {
8581
8939
  type: "string",
8582
- description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
8940
+ description: "Lease token; omit for a tab this process already holds. See server instructions."
8583
8941
  },
8584
8942
  target: {
8585
8943
  type: "string",
8586
- description: "Page selector: 'active' (default), 'index:N', 'url:<substr>', 'title:<substr>', 'label:<name>', or a 32-hex '<targetId>'. Grammar: see server instructions."
8944
+ description: "Page selector (index:/url:/title:/label:/targetId, default active); grammar in server instructions."
8587
8945
  },
8588
8946
  format: {
8589
8947
  type: "string",
@@ -8591,27 +8949,27 @@ var MANIFEST = [
8591
8949
  "jpeg",
8592
8950
  "png"
8593
8951
  ],
8594
- description: "Frame format, default jpeg (much smaller per repaint); png is lossless. quality applies to jpeg only."
8952
+ description: "Image/frame format."
8595
8953
  },
8596
8954
  quality: {
8597
8955
  type: "number",
8598
- description: "JPEG frame quality 0-100. Ignored for png; omitted leaves Chrome's own default."
8956
+ description: "JPEG quality 0-100."
8599
8957
  },
8600
8958
  maxWidth: {
8601
8959
  type: "number",
8602
- description: "Caps the streamed frame width in px; also pins frame size if the viewport may change mid-recording."
8960
+ description: "Maximum frame width."
8603
8961
  },
8604
8962
  maxHeight: {
8605
8963
  type: "number",
8606
- description: "Caps the streamed frame height in px."
8964
+ description: "Maximum frame height."
8607
8965
  },
8608
8966
  everyNthFrame: {
8609
8967
  type: "number",
8610
- description: "Capture only every Nth repaint (1=every frame), to shrink spool size on a busy page; per-frame durations keep playback at wallclock speed either way."
8968
+ description: "Capture every Nth frame."
8611
8969
  },
8612
8970
  bringToFront: {
8613
8971
  type: "boolean",
8614
- description: "Activate the tab (Page.bringToFront) before recording, default false. A backgrounded/occluded tab may never repaint, yielding a 0-frame recording."
8972
+ description: "Activate tab before recording."
8615
8973
  }
8616
8974
  },
8617
8975
  required: [],
@@ -8620,21 +8978,21 @@ var MANIFEST = [
8620
8978
  },
8621
8979
  {
8622
8980
  name: "stop_screen_recording",
8623
- description: "Stops the recording started by start_screen_recording, assembling spooled frames into an MP4 via ffmpeg with PER-FRAME durations (frame N held until N+1 painted; last frame held until this call) — capped at 25fps by ffmpeg's concat demuxer, so frames under 40ms apart are deliberately coalesced (frameCount=captured, encodedFrames=what reached the video, never silently dropped). Returns {path,bytes,durationMs,frameCount,encodedFrames,codec,encoder,width,height,droppedFrames,target}; durationMs sums encoded durations, the file runs one 40ms step longer (final frame repeated). Encoder ladder (probed at start): hevc_videotoolbox>h264_videotoolbox>libx265>libx264 (+hvc1 tag, faststart, yuv420p, even dims). target: omit with one recording in flight, required with several (avoids guessing which to stop). Throws if nothing is recording in this process, or if 0 frames arrived (no silent empty video). On ffmpeg failure the spooled frames are KEPT — spool path + exact command are in the error.",
8981
+ description: "Stop a screen recording and encode MP4; target is required when multiple recordings exist.",
8624
8982
  inputSchema: {
8625
8983
  type: "object",
8626
8984
  properties: {
8627
8985
  lease: {
8628
8986
  type: "string",
8629
- description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
8987
+ description: "Lease token; omit for a tab this process already holds. See server instructions."
8630
8988
  },
8631
8989
  target: {
8632
8990
  type: "string",
8633
- description: "Which recording to stop — 'active' valid only when exactly one recording is in flight. Grammar: server instructions."
8991
+ description: "Page selector (index:/url:/title:/label:/targetId, default active); grammar in server instructions."
8634
8992
  },
8635
8993
  savePath: {
8636
8994
  type: "string",
8637
- description: "Override the output path (default /tmp/cdp-toolkit/screen-recording-<targetIdShort>-<stamp>.mp4)."
8995
+ description: "Output file path."
8638
8996
  }
8639
8997
  },
8640
8998
  required: [],
@@ -8643,21 +9001,21 @@ var MANIFEST = [
8643
9001
  },
8644
9002
  {
8645
9003
  name: "take_heapsnapshot",
8646
- description: "Captures a V8 heap snapshot of the target page over CDP and writes it as a .heapsnapshot JSON file (loadable in the DevTools Memory panel) under /tmp/cdp-toolkit unless savePath is given. Returns {path,bytes,chunks,target} only — does NOT parse or summarize the snapshot.",
9004
+ description: "Capture a V8 heap snapshot to JSON; the snapshot is not parsed.",
8647
9005
  inputSchema: {
8648
9006
  type: "object",
8649
9007
  properties: {
8650
9008
  lease: {
8651
9009
  type: "string",
8652
- description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
9010
+ description: "Lease token; omit for a tab this process already holds. See server instructions."
8653
9011
  },
8654
9012
  target: {
8655
9013
  type: "string",
8656
- description: "Page selector: 'active' (default), 'index:N', 'url:<substr>', 'title:<substr>', 'label:<name>', or a 32-hex '<targetId>'. Grammar: see server instructions."
9014
+ description: "Page selector (index:/url:/title:/label:/targetId, default active); grammar in server instructions."
8657
9015
  },
8658
9016
  savePath: {
8659
9017
  type: "string",
8660
- description: "Override the output path. Absolute paths (starting with /) are used as-is; relative paths resolve under the artifact dir (/tmp/cdp-toolkit). Defaults to an auto-named take_heapsnapshot-<targetId>-<timestamp>.heapsnapshot file."
9018
+ description: "Output file path."
8661
9019
  }
8662
9020
  },
8663
9021
  required: [],
@@ -8666,28 +9024,28 @@ var MANIFEST = [
8666
9024
  },
8667
9025
  {
8668
9026
  name: "lighthouse_audit",
8669
- description: "Runs a Lighthouse audit against a URL by shelling out to `npx --yes lighthouse` (the toolkit's only non-CDP tool) — it attaches to the already-running Chrome and audits its OWN about:blank tab, NOT any live user tab you've navigated. Writes a JSON report under /tmp/cdp-toolkit; returns {path,bytes} plus per-category scores (0..1 or null).",
9027
+ description: "Run Lighthouse against a URL in its own tab; it does not audit a user tab.",
8670
9028
  inputSchema: {
8671
9029
  type: "object",
8672
9030
  properties: {
8673
9031
  lease: {
8674
9032
  type: "string",
8675
- description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
9033
+ description: "Lease token; omit for a tab this process already holds. See server instructions."
8676
9034
  },
8677
9035
  url: {
8678
9036
  type: "string",
8679
- description: "The URL to audit. Required — never points at a user tab implicitly."
9037
+ description: "URL value."
8680
9038
  },
8681
9039
  categories: {
8682
9040
  type: "array",
8683
9041
  items: {
8684
9042
  type: "string"
8685
9043
  },
8686
- description: 'Lighthouse categories to run (--only-categories). Defaults to the full set, e.g. ["performance"] or ["performance","accessibility","seo"].'
9044
+ description: "Lighthouse categories to run (--only-categories)."
8687
9045
  },
8688
9046
  savePath: {
8689
9047
  type: "string",
8690
- description: "Override the report output path; defaults to a timestamped file under /tmp/cdp-toolkit."
9048
+ description: "Output file path."
8691
9049
  },
8692
9050
  formFactor: {
8693
9051
  type: "string",
@@ -8695,11 +9053,11 @@ var MANIFEST = [
8695
9053
  "desktop",
8696
9054
  "mobile"
8697
9055
  ],
8698
- description: '"desktop" (default, uses --preset=desktop to avoid heavy mobile throttling) or "mobile".'
9056
+ description: "Lighthouse form factor."
8699
9057
  },
8700
9058
  timeoutMs: {
8701
9059
  type: "number",
8702
- description: "Overall budget for the lighthouse process, in ms. Default 120000."
9060
+ description: "Timeout in milliseconds."
8703
9061
  }
8704
9062
  },
8705
9063
  required: [
@@ -8710,21 +9068,21 @@ var MANIFEST = [
8710
9068
  },
8711
9069
  {
8712
9070
  name: "mock_request",
8713
- description: "Register a mock rule on a target's persistent fake-backend session (CDP Fetch domain): match requests by urlPattern and fulfill with a canned response, fail (abort), or continue -- optionally with fault injection (delayMs/failRate). The session survives reloads/navigations and lives until clear_mocks; call repeatedly to mock several endpoints on one target. reload:true applies it immediately. Persistent across calls only via the MCP server, not the one-shot CLI.",
9071
+ description: "Register a persistent request mock; urlPattern is required.",
8714
9072
  inputSchema: {
8715
9073
  type: "object",
8716
9074
  properties: {
8717
9075
  lease: {
8718
9076
  type: "string",
8719
- description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
9077
+ description: "Lease token; omit for a tab this process already holds. See server instructions."
8720
9078
  },
8721
9079
  target: {
8722
9080
  type: "string",
8723
- description: "Which page's mock session. Defaults to the active page. Grammar: server instructions."
9081
+ description: "Page selector (index:/url:/title:/label:/targetId, default active); grammar in server instructions."
8724
9082
  },
8725
9083
  urlPattern: {
8726
9084
  type: "string",
8727
- description: `CDP Fetch urlPattern glob: '*' any run of chars, '?' exactly one, '\\' escapes. Only matching URLs are paused. e.g. "*/api/users*".`
9085
+ description: "URL glob (*, ?; \\ escapes)."
8728
9086
  },
8729
9087
  action: {
8730
9088
  type: "string",
@@ -8737,39 +9095,39 @@ var MANIFEST = [
8737
9095
  },
8738
9096
  status: {
8739
9097
  type: "number",
8740
- description: "fulfill: HTTP status for the canned response. Default 200."
9098
+ description: "Mock HTTP status."
8741
9099
  },
8742
9100
  body: {
8743
9101
  type: "string",
8744
- description: "fulfill: response body string (base64-encoded for CDP internally)."
9102
+ description: "Mock response body."
8745
9103
  },
8746
9104
  contentType: {
8747
9105
  type: "string",
8748
- description: 'fulfill: Content-Type header. Default "application/json".'
9106
+ description: "Response Content-Type."
8749
9107
  },
8750
9108
  headers: {
8751
9109
  type: "object",
8752
- description: 'fulfill: extra response headers (name->value); override Content-Type case-insensitively. Add "Access-Control-Allow-Origin" for cross-origin fetches.'
9110
+ description: "Extra response headers."
8753
9111
  },
8754
9112
  errorReason: {
8755
9113
  type: "string",
8756
- description: 'fail: CDP Network.ErrorReason, e.g. "Failed" (default), "BlockedByClient", "ConnectionRefused", "TimedOut".'
9114
+ description: "CDP Network.ErrorReason, e.g. Failed|BlockedByClient|ConnectionRefused|TimedOut."
8757
9115
  },
8758
9116
  method: {
8759
9117
  type: "string",
8760
- description: 'Only mock this HTTP method (e.g. "POST"); others pass through.'
9118
+ description: "HTTP method."
8761
9119
  },
8762
9120
  delayMs: {
8763
9121
  type: "number",
8764
- description: "Fault injection: artificial latency in ms before responding."
9122
+ description: "Artificial delay in milliseconds."
8765
9123
  },
8766
9124
  failRate: {
8767
9125
  type: "number",
8768
- description: "Fault injection: probability 0..1 of failing a matched request regardless of action."
9126
+ description: "Failure probability 0..1."
8769
9127
  },
8770
9128
  reload: {
8771
9129
  type: "boolean",
8772
- description: "Reload the target after arming so the mock catches traffic immediately."
9130
+ description: "Reload before capture."
8773
9131
  }
8774
9132
  },
8775
9133
  required: [
@@ -8780,13 +9138,13 @@ var MANIFEST = [
8780
9138
  },
8781
9139
  {
8782
9140
  name: "list_mocks",
8783
- description: "List active fake-backend sessions (one per target) with their rules and hit counts. Prunes sessions whose tab has closed.",
9141
+ description: "List active request-mocking sessions and hit counts.",
8784
9142
  inputSchema: {
8785
9143
  type: "object",
8786
9144
  properties: {
8787
9145
  lease: {
8788
9146
  type: "string",
8789
- description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
9147
+ description: "Lease token; omit for a tab this process already holds. See server instructions."
8790
9148
  },
8791
9149
  target: {
8792
9150
  type: "string",
@@ -8799,17 +9157,17 @@ var MANIFEST = [
8799
9157
  },
8800
9158
  {
8801
9159
  name: "clear_mocks",
8802
- description: "Tear down fake-backend sessions (Fetch.disable + close the connection). Clears the resolved target's session by default, or every active session with all:true.",
9160
+ description: "Clear the selected or all request-mocking sessions.",
8803
9161
  inputSchema: {
8804
9162
  type: "object",
8805
9163
  properties: {
8806
9164
  lease: {
8807
9165
  type: "string",
8808
- description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
9166
+ description: "Lease token; omit for a tab this process already holds. See server instructions."
8809
9167
  },
8810
9168
  target: {
8811
9169
  type: "string",
8812
- description: "Which page's mock session to clear. Defaults to the active page; ignored when all:true. Grammar: server instructions."
9170
+ description: "Page selector (index:/url:/title:/label:/targetId, default active); grammar in server instructions."
8813
9171
  },
8814
9172
  all: {
8815
9173
  type: "boolean",
@@ -8822,29 +9180,29 @@ var MANIFEST = [
8822
9180
  },
8823
9181
  {
8824
9182
  name: "claim_page",
8825
- description: "Take exclusive ownership of a browser tab, returning an opaque lease token (needed once under CDP_REQUIRE_LEASE for any tab you drive). Two modes: FRESH TAB (no target - opens one, optionally at url, and claims it: 'give me my own tab' in one call) and TAKEOVER (target - claims an already-open tab, e.g. the human's, resolving the selector against the live page list, creating nothing). The opened flag is true only when this call created the tab, and release_page consults that record, so a taken-over tab is left open on release. Refused from the CLI (its per-call process would make the lease instantly reclaimable). The answer carries humanActiveMs, ms since input this server did not dispatch; null is NO DATA (no activity beacon, or every input was this server's own) and never means 'no human'; absent on a backend that cannot answer. On takeover of a tab a human used within 30s it adds contention, a warning - but THE CLAIM IS NEVER REFUSED FOR THIS: takeover is its purpose, you already hold the lease; it only warns you're fighting a live person, so open your own tab or ask first. Limits: input inside a cross-origin iframe is invisible, and a second MCP server's clicks read as human.",
9183
+ description: "Claim an existing tab or open a fresh one; CLI use is refused.",
8826
9184
  inputSchema: {
8827
9185
  type: "object",
8828
9186
  properties: {
8829
9187
  target: {
8830
9188
  type: "string",
8831
- description: "Take over an already-open tab (e.g. one the human has open). Resolved against the LIVE page list only, so it never opens a tab — a selector matching nothing is an error. Left open on release. Refused if another live process holds it (including a lease auto-acquired under CDP_REQUIRE_LEASE): takes over unleased human tabs, never steals from a live agent. Mutually exclusive with targetId. Selector grammar: server instructions."
9189
+ description: "Page selector (index:/url:/title:/label:/targetId, default active); grammar in server instructions."
8832
9190
  },
8833
9191
  targetId: {
8834
9192
  type: "string",
8835
- description: "Exact target id (not a selector) of an already-open page to claim instead of opening one. Back-compat only; prefer target, which also accepts an exact id. Mutually exclusive with target."
9193
+ description: "Exact target ID."
8836
9194
  },
8837
9195
  url: {
8838
9196
  type: "string",
8839
- description: "When opening a fresh tab, navigate it here. Ignored when target or targetId is given."
9197
+ description: "URL value."
8840
9198
  },
8841
9199
  label: {
8842
9200
  type: "string",
8843
- description: "Agent label recorded on the lease; surfaced in conflict errors and list_leases. Defaults to pid-<pid>."
9201
+ description: "Agent lease label."
8844
9202
  },
8845
9203
  ttlMs: {
8846
9204
  type: "number",
8847
- description: "How long the lease survives without use before it is reclaimable. Defaults to CDP_LEASE_TTL_MS, else 900000 (15min); every checked call refreshes it."
9205
+ description: "Lease TTL in milliseconds."
8848
9206
  }
8849
9207
  },
8850
9208
  required: [],
@@ -8853,21 +9211,21 @@ var MANIFEST = [
8853
9211
  },
8854
9212
  {
8855
9213
  name: "release_page",
8856
- description: "Give a lease back, closing the tab only if this toolkit opened it. Take exactly one of lease (the token) or target (a selector for a tab this process holds - use this to release a lease the gate acquired for you automatically, since that path never handed you a token). A tab with a creation record from this toolkit is closed; a tab that was already open and merely claimed is released and left alone - override either way with close. Idempotent: an already-released, reclaimed, or expired lease reports released:false and closes nothing (by then the tab may belong to another agent). Answers {released, closed, targetId}.",
9214
+ description: "Release a page lease and optionally close its tab; provide lease or target.",
8857
9215
  inputSchema: {
8858
9216
  type: "object",
8859
9217
  properties: {
8860
9218
  lease: {
8861
9219
  type: "string",
8862
- description: "Token from claim_page / new_page{claim:true}. Mutually exclusive with 'target'. Under CDP_REQUIRE_LEASE an auto-acquired lease never produced a token — give 'target' instead."
9220
+ description: "Lease token; omit for a tab this process already holds. See server instructions."
8863
9221
  },
8864
9222
  target: {
8865
9223
  type: "string",
8866
- description: "A tab THIS process holds (grammar: server instructions). Mutually exclusive with 'lease'. Refused if another process holds the tab."
9224
+ description: "Page selector (index:/url:/title:/label:/targetId, default active); grammar in server instructions."
8867
9225
  },
8868
9226
  close: {
8869
9227
  type: "boolean",
8870
- description: "Force the close decision instead of letting the creation record decide: true closes even a tab this toolkit did not open; false keeps an agent-created tab open. Omit to close only tabs this toolkit created."
9228
+ description: "Override close behavior."
8871
9229
  }
8872
9230
  },
8873
9231
  required: [],
@@ -8876,7 +9234,7 @@ var MANIFEST = [
8876
9234
  },
8877
9235
  {
8878
9236
  name: "list_leases",
8879
- description: "Enumerate every active lease for diagnosis - backend, target id, label, pid, createdAt, lastUsedAt, ttlMs, whether the owning process is alive, and whether the lease is reclaimable and why. No token required; it never returns the lease nonce, so it cannot be used to forge one. An unreadable or unparseable lease row instead carries unreadable, with label, pid and timestamps zeroed and stale forced false (an unreadable lease must never read as free to take). Under CDP_REQUIRE_LEASE it reaps abandoned agent tabs exactly as list_pages does, into an additive reaped array, omitting their rows. Where the tab can answer, a row also carries humanActiveMs, ms since input this server did not dispatch - note lastUsedAt tracks TOOLKIT calls only, so a tab a person has typed in for ten minutes looks idle, which is what this exposes; the field is absent, never null, when the tab cannot answer, and that absence means 'no answer', never 'nobody is there'. idleMs (now - lastUsedAt) and expiresAt (lastUsedAt + ttlMs) are computed fresh each call and omitted on an unreadable row.",
9237
+ description: "List active leases for diagnosis without exposing lease tokens.",
8880
9238
  inputSchema: {
8881
9239
  type: "object",
8882
9240
  properties: {},
@@ -8886,6 +9244,566 @@ var MANIFEST = [
8886
9244
  }
8887
9245
  ];
8888
9246
 
9247
+ // src/install/wizard.ts
9248
+ import { createInterface } from "node:readline";
9249
+ import { homedir as homedir3 } from "node:os";
9250
+ import { join as join14 } from "node:path";
9251
+
9252
+ // src/install/browser.ts
9253
+ import { existsSync as existsSync2 } from "node:fs";
9254
+ import { mkdir as mkdir13, readFile as readFile9, writeFile as writeFile10 } from "node:fs/promises";
9255
+ import { homedir } from "node:os";
9256
+ import { delimiter, dirname as dirname3, join as join12 } from "node:path";
9257
+ var CANDIDATE_BUILDERS = {
9258
+ chrome: () => [
9259
+ process.env.CHROME_BIN,
9260
+ "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
9261
+ "google-chrome",
9262
+ "google-chrome-stable",
9263
+ "chromium"
9264
+ ],
9265
+ arc: () => [process.env.ARC_BIN, "/Applications/Arc.app/Contents/MacOS/Arc", "arc"],
9266
+ firefox: () => [
9267
+ process.env.FIREFOX_BIN,
9268
+ "/Applications/Firefox.app/Contents/MacOS/firefox",
9269
+ "firefox",
9270
+ "/usr/bin/firefox"
9271
+ ]
9272
+ };
9273
+ function existsOnPath(cmd) {
9274
+ const pathEnv = process.env.PATH;
9275
+ if (!pathEnv)
9276
+ return false;
9277
+ for (const dir of pathEnv.split(delimiter)) {
9278
+ if (!dir)
9279
+ continue;
9280
+ if (existsSync2(join12(dir, cmd)))
9281
+ return true;
9282
+ }
9283
+ return false;
9284
+ }
9285
+ function resolveCandidate(candidates) {
9286
+ let lastDefined = "";
9287
+ for (const c of candidates) {
9288
+ if (!c)
9289
+ continue;
9290
+ lastDefined = c;
9291
+ if (c.startsWith("/")) {
9292
+ if (existsSync2(c))
9293
+ return { binary: c, found: true };
9294
+ continue;
9295
+ }
9296
+ if (existsOnPath(c))
9297
+ return { binary: c, found: true };
9298
+ }
9299
+ return { binary: lastDefined, found: false };
9300
+ }
9301
+ function detectBrowser(choice) {
9302
+ const { binary, found } = resolveCandidate(CANDIDATE_BUILDERS[choice]());
9303
+ return { choice, binary, found };
9304
+ }
9305
+ function defaultProfileDir(choice) {
9306
+ return `$HOME/.cdp-toolkit/${choice}-profile`;
9307
+ }
9308
+ function defaultAliasName(choice) {
9309
+ return `cdp-${choice}`;
9310
+ }
9311
+ function browserLaunchArgs(o) {
9312
+ const profileDir = o.profileDir ?? defaultProfileDir(o.choice);
9313
+ if (o.choice === "firefox") {
9314
+ return ["--remote-debugging-port", String(o.port), "--marionette", "--no-remote", "--profile", profileDir];
9315
+ }
9316
+ return [`--remote-debugging-port=${o.port}`, `--user-data-dir=${profileDir}`];
9317
+ }
9318
+ function shellQuoteIfSpaced(token) {
9319
+ return /\s/.test(token) ? `"${token}"` : token;
9320
+ }
9321
+ function aliasLine(o) {
9322
+ const name = o.aliasName ?? defaultAliasName(o.choice);
9323
+ const { binary } = detectBrowser(o.choice);
9324
+ const parts = [shellQuoteIfSpaced(binary), ...browserLaunchArgs(o)];
9325
+ return `alias ${name}='${parts.join(" ")}'`;
9326
+ }
9327
+ function detectShellRc() {
9328
+ const shell = process.env.SHELL ?? "";
9329
+ if (shell.endsWith("zsh"))
9330
+ return join12(homedir(), ".zshrc");
9331
+ if (shell.endsWith("bash"))
9332
+ return join12(homedir(), ".bashrc");
9333
+ return join12(homedir(), process.platform === "darwin" ? ".zshrc" : ".bashrc");
9334
+ }
9335
+ var MARKER_START = "# >>> cdp-toolkit alias >>>";
9336
+ var MARKER_END = "# <<< cdp-toolkit alias <<<";
9337
+ function ensureTrailingNewline(s) {
9338
+ return s.endsWith(`
9339
+ `) ? s : `${s}
9340
+ `;
9341
+ }
9342
+ async function appendAliasToRc(rcPath, aliasBlockBody) {
9343
+ let existing = "";
9344
+ try {
9345
+ existing = await readFile9(rcPath, "utf8");
9346
+ } catch (err) {
9347
+ if (err.code !== "ENOENT")
9348
+ throw err;
9349
+ }
9350
+ const block = `${MARKER_START}
9351
+ ${aliasBlockBody}
9352
+ ${MARKER_END}`;
9353
+ const startIdx = existing.indexOf(MARKER_START);
9354
+ const endIdx = existing.indexOf(MARKER_END);
9355
+ if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
9356
+ const endOfEndLine = existing.indexOf(`
9357
+ `, endIdx);
9358
+ const before = existing.slice(0, startIdx);
9359
+ const after = endOfEndLine === -1 ? "" : existing.slice(endOfEndLine + 1);
9360
+ const existingBlock = existing.slice(startIdx, endOfEndLine === -1 ? existing.length : endOfEndLine);
9361
+ if (existingBlock === block) {
9362
+ return { rcPath, action: "unchanged" };
9363
+ }
9364
+ const next2 = ensureTrailingNewline(`${before}${block}
9365
+ ${after}`);
9366
+ await writeFile10(rcPath, next2, "utf8");
9367
+ return { rcPath, action: "replaced-block" };
9368
+ }
9369
+ await mkdir13(dirname3(rcPath), { recursive: true });
9370
+ const sep = existing.length === 0 || existing.endsWith(`
9371
+ `) ? "" : `
9372
+ `;
9373
+ const next = `${existing}${sep}${block}
9374
+ `;
9375
+ await writeFile10(rcPath, next, "utf8");
9376
+ return { rcPath, action: "created-block" };
9377
+ }
9378
+
9379
+ // src/install/harness.ts
9380
+ import { spawnSync } from "node:child_process";
9381
+ import { mkdir as mkdir14, readFile as readFile10, writeFile as writeFile11 } from "node:fs/promises";
9382
+ import { homedir as homedir2 } from "node:os";
9383
+ import { dirname as dirname4, join as join13 } from "node:path";
9384
+ async function installMcpServer(harness, name, cfg, opts = {}) {
9385
+ switch (harness) {
9386
+ case "claude":
9387
+ return installClaude(name, cfg, opts);
9388
+ case "codex":
9389
+ return installCodex(name, cfg);
9390
+ case "opencode":
9391
+ return installOpencode(name, cfg);
9392
+ default: {
9393
+ const exhaustive = harness;
9394
+ throw new Error(`unknown harness: ${String(exhaustive)}`);
9395
+ }
9396
+ }
9397
+ }
9398
+ async function installClaude(name, cfg, opts) {
9399
+ const path = opts.configPath ?? join13(opts.homeDir ?? homedir2(), ".claude.json");
9400
+ let root;
9401
+ let fileExisted;
9402
+ try {
9403
+ const text = await readFile10(path, "utf8");
9404
+ const parsed = JSON.parse(text);
9405
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
9406
+ throw new Error(`${path} does not contain a JSON object at its top level`);
9407
+ }
9408
+ root = parsed;
9409
+ fileExisted = true;
9410
+ } catch (err) {
9411
+ if (err.code === "ENOENT") {
9412
+ root = {};
9413
+ fileExisted = false;
9414
+ } else {
9415
+ throw err;
9416
+ }
9417
+ }
9418
+ const rawServers = root.mcpServers;
9419
+ const mcpServers = typeof rawServers === "object" && rawServers !== null && !Array.isArray(rawServers) ? rawServers : {};
9420
+ root.mcpServers = mcpServers;
9421
+ const entry = { command: cfg.command, args: cfg.args };
9422
+ if (Object.keys(cfg.env).length > 0)
9423
+ entry.env = cfg.env;
9424
+ const prior = mcpServers[name];
9425
+ const action = prior === undefined ? "added" : canonicalJson(prior) === canonicalJson(entry) ? "unchanged" : "updated";
9426
+ mcpServers[name] = entry;
9427
+ if (action === "unchanged") {
9428
+ return { harness: "claude", method: "file", path, action, detail: "no change needed" };
9429
+ }
9430
+ await mkdir14(dirname4(path), { recursive: true });
9431
+ await writeFile11(path, JSON.stringify(root, null, 2) + `
9432
+ `, "utf8");
9433
+ return {
9434
+ harness: "claude",
9435
+ method: "file",
9436
+ path,
9437
+ action,
9438
+ detail: fileExisted ? undefined : "created new config file"
9439
+ };
9440
+ }
9441
+ function canonicalJson(value) {
9442
+ return JSON.stringify(canonicalize(value));
9443
+ }
9444
+ function canonicalize(value) {
9445
+ if (Array.isArray(value))
9446
+ return value.map(canonicalize);
9447
+ if (value !== null && typeof value === "object") {
9448
+ const out = {};
9449
+ for (const key of Object.keys(value).sort()) {
9450
+ out[key] = canonicalize(value[key]);
9451
+ }
9452
+ return out;
9453
+ }
9454
+ return value;
9455
+ }
9456
+ function codexAddArgs(name, cfg) {
9457
+ const envArgs = Object.entries(cfg.env).flatMap(([k, v]) => ["--env", `${k}=${v}`]);
9458
+ return ["mcp", "add", name, ...envArgs, "--", cfg.command, ...cfg.args];
9459
+ }
9460
+ async function installCodex(name, cfg) {
9461
+ if (!isOnPath("codex")) {
9462
+ throw new Error("codex CLI not found on PATH — install Codex or pick a different harness");
9463
+ }
9464
+ const probe = spawnSync("codex", ["mcp", "get", name], { encoding: "utf8" });
9465
+ const exists = probe.status === 0;
9466
+ if (exists) {
9467
+ const removed = spawnSync("codex", ["mcp", "remove", name], { encoding: "utf8" });
9468
+ if (removed.status !== 0) {
9469
+ throw new Error(`codex mcp remove ${name} failed: ${(removed.stderr || removed.stdout || "").trim()}`);
9470
+ }
9471
+ }
9472
+ const addArgs = codexAddArgs(name, cfg);
9473
+ const added = spawnSync("codex", addArgs, { encoding: "utf8" });
9474
+ if (added.status !== 0) {
9475
+ throw new Error(`codex ${addArgs.join(" ")} failed: ${(added.stderr || added.stdout || "").trim()}`);
9476
+ }
9477
+ return {
9478
+ harness: "codex",
9479
+ method: "cli",
9480
+ action: exists ? "updated" : "added",
9481
+ detail: added.stdout?.trim() || undefined
9482
+ };
9483
+ }
9484
+ function opencodeAddArgs(name, cfg) {
9485
+ const envArgs = Object.entries(cfg.env).flatMap(([k, v]) => ["--env", `${k}=${v}`]);
9486
+ return ["mcp", "add", name, ...envArgs, "--", cfg.command, ...cfg.args];
9487
+ }
9488
+ async function installOpencode(name, cfg) {
9489
+ if (!isOnPath("opencode")) {
9490
+ throw new Error("opencode CLI not found on PATH — install OpenCode or pick a different harness");
9491
+ }
9492
+ const addArgs = opencodeAddArgs(name, cfg);
9493
+ const added = spawnSync("opencode", addArgs, { encoding: "utf8" });
9494
+ if (added.status !== 0) {
9495
+ throw new Error(`opencode ${addArgs.join(" ")} failed: ${(added.stderr || added.stdout || "").trim()}`);
9496
+ }
9497
+ return {
9498
+ harness: "opencode",
9499
+ method: "cli",
9500
+ action: "added",
9501
+ detail: added.stdout?.trim() || undefined
9502
+ };
9503
+ }
9504
+ function isOnPath(bin) {
9505
+ const finder = process.platform === "win32" ? "where" : "which";
9506
+ const result = spawnSync(finder, [bin], { encoding: "utf8" });
9507
+ return result.status === 0;
9508
+ }
9509
+
9510
+ // src/install/wizard.ts
9511
+ var DEFAULT_NAME = "cdp-toolkit";
9512
+ var HARNESSES = ["claude", "codex", "opencode"];
9513
+ var BROWSERS = ["arc", "chrome", "firefox"];
9514
+ function defaultPortFor(browser) {
9515
+ return browser === "firefox" ? 9223 : 9222;
9516
+ }
9517
+ function parseHarness(v) {
9518
+ if (v === undefined)
9519
+ throw new Error("--harness requires a value: claude, codex, or opencode");
9520
+ if (HARNESSES.includes(v))
9521
+ return v;
9522
+ throw new Error(`--harness must be one of claude|codex|opencode (got '${v}')`);
9523
+ }
9524
+ function parseBrowserChoice(v) {
9525
+ if (v === undefined)
9526
+ throw new Error("--browser requires a value: arc, chrome, or firefox");
9527
+ if (BROWSERS.includes(v))
9528
+ return v;
9529
+ throw new Error(`--browser must be one of arc|chrome|firefox (got '${v}')`);
9530
+ }
9531
+ function parsePort(v) {
9532
+ if (v === undefined)
9533
+ throw new Error("--port requires a value (an integer 1-65535)");
9534
+ const n = Number(v);
9535
+ if (!Number.isInteger(n) || n < 1 || n > 65535) {
9536
+ throw new Error(`--port must be an integer 1-65535 (got '${v}')`);
9537
+ }
9538
+ return n;
9539
+ }
9540
+ function normalizeArgv(argv) {
9541
+ const out = [];
9542
+ for (const t of argv) {
9543
+ if (t.startsWith("--") && t.includes("=")) {
9544
+ const eq = t.indexOf("=");
9545
+ out.push(t.slice(0, eq), t.slice(eq + 1));
9546
+ } else {
9547
+ out.push(t);
9548
+ }
9549
+ }
9550
+ return out;
9551
+ }
9552
+ function parseInstallArgs(argv) {
9553
+ const tokens = normalizeArgv(argv);
9554
+ const out = { name: DEFAULT_NAME, alias: true };
9555
+ for (let i = 0;i < tokens.length; i++) {
9556
+ const t = tokens[i];
9557
+ switch (t) {
9558
+ case "--help":
9559
+ case "-h":
9560
+ out.help = true;
9561
+ break;
9562
+ case "--yes":
9563
+ case "-y":
9564
+ out.yes = true;
9565
+ break;
9566
+ case "--no-alias":
9567
+ out.alias = false;
9568
+ break;
9569
+ case "--alias":
9570
+ out.alias = true;
9571
+ break;
9572
+ case "--harness":
9573
+ out.harness = parseHarness(tokens[++i]);
9574
+ break;
9575
+ case "--browser":
9576
+ out.browser = parseBrowserChoice(tokens[++i]);
9577
+ break;
9578
+ case "--port":
9579
+ out.port = parsePort(tokens[++i]);
9580
+ break;
9581
+ case "--name": {
9582
+ const v = tokens[++i];
9583
+ if (v === undefined)
9584
+ throw new Error("--name requires a value");
9585
+ out.name = v;
9586
+ break;
9587
+ }
9588
+ case "--alias-name": {
9589
+ const v = tokens[++i];
9590
+ if (v === undefined)
9591
+ throw new Error("--alias-name requires a value");
9592
+ out.aliasName = v;
9593
+ break;
9594
+ }
9595
+ default:
9596
+ throw new Error(`cdp install: unknown option '${t}' (run \`cdp install --help\`)`);
9597
+ }
9598
+ }
9599
+ return out;
9600
+ }
9601
+ function composeServerConfig(browser, port) {
9602
+ const env = browser === "firefox" ? { CDP_BROWSER: "firefox", CDP_FIREFOX_ENDPOINT: String(port) } : { CDP_BASE: `http://127.0.0.1:${port}` };
9603
+ return { command: "npx", args: ["-y", "cdp-toolkit"], env };
9604
+ }
9605
+ function installHelpText() {
9606
+ return `cdp install — register the cdp-toolkit MCP server with your coding harness and add a
9607
+ browser-launch alias (with the remote-debugging port open) to your shell rc.
9608
+
9609
+ Usage:
9610
+ cdp install [--harness <claude|codex|opencode>] [--browser <arc|chrome|firefox>]
9611
+ [--port <n>] [--name <server-name>] [--alias-name <name>]
9612
+ [--no-alias] [--yes|-y] [--help|-h]
9613
+
9614
+ Options:
9615
+ --harness <h> Coding harness to register the MCP server with: claude, codex, opencode.
9616
+ --browser <b> Browser the alias launches: arc, chrome, firefox.
9617
+ --port <n> Remote-debugging port (1-65535). Default: 9222 (chrome/arc), 9223 (firefox).
9618
+ --name <name> MCP server name to register. Default: ${DEFAULT_NAME}.
9619
+ --alias-name <n> Shell alias name. Default: cdp-<browser> (e.g. cdp-firefox).
9620
+ --no-alias Register the MCP server only; skip writing the shell alias.
9621
+ --yes, -y Skip the confirmation prompt (proceed immediately).
9622
+ --help, -h Show this help.
9623
+
9624
+ Interactive: run \`cdp install\` in a terminal with no flags and it prompts for harness, browser,
9625
+ and port, then asks before writing anything.
9626
+
9627
+ Non-interactive (piped/CI): pass at least --harness and --browser (port defaults per browser); a
9628
+ missing required value fails fast with usage instead of hanging on stdin.`;
9629
+ }
9630
+ async function ask(question) {
9631
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
9632
+ try {
9633
+ return await new Promise((resolve) => rl.question(question, resolve));
9634
+ } finally {
9635
+ rl.close();
9636
+ }
9637
+ }
9638
+ async function promptMenu(label, options) {
9639
+ const lines = [label];
9640
+ options.forEach((o, i) => lines.push(` ${i + 1}) ${o}`));
9641
+ process.stdout.write(`${lines.join(`
9642
+ `)}
9643
+ `);
9644
+ for (;; ) {
9645
+ const ans = (await ask(`Enter 1-${options.length}: `)).trim();
9646
+ const n = Number(ans);
9647
+ if (Number.isInteger(n) && n >= 1 && n <= options.length)
9648
+ return options[n - 1];
9649
+ process.stdout.write(`Please enter a number between 1 and ${options.length}.
9650
+ `);
9651
+ }
9652
+ }
9653
+ async function promptPort(def) {
9654
+ for (;; ) {
9655
+ const ans = (await ask(`Remote-debugging port [${def}]: `)).trim();
9656
+ if (ans === "")
9657
+ return def;
9658
+ const n = Number(ans);
9659
+ if (Number.isInteger(n) && n >= 1 && n <= 65535)
9660
+ return n;
9661
+ process.stdout.write(`Please enter an integer between 1 and 65535.
9662
+ `);
9663
+ }
9664
+ }
9665
+ function harnessTargetDesc(harness, io) {
9666
+ if (harness === "claude") {
9667
+ return io.configPath ?? join14(io.homeDir ?? homedir3(), ".claude.json");
9668
+ }
9669
+ return `via \`${harness} mcp add\``;
9670
+ }
9671
+ function binEnvVar(browser) {
9672
+ return `${browser.toUpperCase()}_BIN`;
9673
+ }
9674
+ async function runInstaller(argv, io = {}) {
9675
+ const write = io.stdout ?? ((s) => void process.stdout.write(s));
9676
+ const writeErr = io.stderr ?? ((s) => void process.stderr.write(s));
9677
+ const isTTY = io.isTTY ?? Boolean(process.stdin.isTTY);
9678
+ let args;
9679
+ try {
9680
+ args = parseInstallArgs(argv);
9681
+ } catch (err) {
9682
+ writeErr(`${err.message}
9683
+
9684
+ `);
9685
+ writeErr(`${installHelpText()}
9686
+ `);
9687
+ return 1;
9688
+ }
9689
+ if (args.help) {
9690
+ write(`${installHelpText()}
9691
+ `);
9692
+ return 0;
9693
+ }
9694
+ let harness = args.harness;
9695
+ let browser = args.browser;
9696
+ let port = args.port;
9697
+ if (isTTY) {
9698
+ if (harness === undefined)
9699
+ harness = await promptMenu("Which coding harness?", HARNESSES);
9700
+ if (browser === undefined)
9701
+ browser = await promptMenu("Which browser?", BROWSERS);
9702
+ if (port === undefined)
9703
+ port = await promptPort(defaultPortFor(browser));
9704
+ } else {
9705
+ const missing = [];
9706
+ if (harness === undefined)
9707
+ missing.push("--harness");
9708
+ if (browser === undefined)
9709
+ missing.push("--browser");
9710
+ if (missing.length > 0) {
9711
+ writeErr(`cdp install: missing required option(s) ${missing.join(", ")} in non-interactive mode.
9712
+ `);
9713
+ writeErr("Run `cdp install` in a terminal to be prompted, or pass the option(s) explicitly.\n\n");
9714
+ writeErr(`${installHelpText()}
9715
+ `);
9716
+ return 1;
9717
+ }
9718
+ if (port === undefined)
9719
+ port = defaultPortFor(browser);
9720
+ }
9721
+ if (harness === undefined || browser === undefined || port === undefined) {
9722
+ writeErr(`cdp install: internal error resolving options
9723
+ `);
9724
+ return 1;
9725
+ }
9726
+ const name = args.name ?? DEFAULT_NAME;
9727
+ const writeAlias = args.alias !== false;
9728
+ const rcPath = io.rcPath ?? detectShellRc();
9729
+ const resolvedAliasName = args.aliasName ?? defaultAliasName(browser);
9730
+ const line = aliasLine({ choice: browser, port, aliasName: args.aliasName });
9731
+ if (!args.yes && isTTY) {
9732
+ const plan = [
9733
+ "",
9734
+ "About to:",
9735
+ ` - Register MCP server "${name}" with ${harness} (${harnessTargetDesc(harness, io)})`,
9736
+ writeAlias ? ` - Append a shell alias to ${rcPath}:
9737
+ ${line}` : " - (skipping shell alias: --no-alias)",
9738
+ ""
9739
+ ].join(`
9740
+ `);
9741
+ write(`${plan}
9742
+ `);
9743
+ const ans = (await ask("Proceed? [y/N] ")).trim();
9744
+ if (!/^y(es)?$/i.test(ans)) {
9745
+ write(`Aborted. Nothing was written.
9746
+ `);
9747
+ return 0;
9748
+ }
9749
+ }
9750
+ let harnessResult;
9751
+ try {
9752
+ harnessResult = await installMcpServer(harness, name, composeServerConfig(browser, port), {
9753
+ configPath: io.configPath,
9754
+ homeDir: io.homeDir
9755
+ });
9756
+ } catch (err) {
9757
+ writeErr(`cdp install: could not register the MCP server with ${harness}: ${err.message}
9758
+ `);
9759
+ return 1;
9760
+ }
9761
+ let rcResult;
9762
+ let browserFound = true;
9763
+ if (writeAlias) {
9764
+ rcResult = await appendAliasToRc(rcPath, line);
9765
+ browserFound = detectBrowser(browser).found;
9766
+ if (!browserFound) {
9767
+ writeErr(`
9768
+ Warning: could not find the ${browser} binary at its expected location. The alias was ` + `written anyway, but \`${resolvedAliasName}\` will not launch until the binary is found.
9769
+ ` + ` Fix: set ${binEnvVar(browser)}=/path/to/${browser} and re-run \`cdp install\`, or edit the
9770
+ ` + ` alias line in ${rcPath} to point at the real binary.
9771
+ `);
9772
+ }
9773
+ }
9774
+ const summary = [
9775
+ "",
9776
+ "cdp-toolkit install complete.",
9777
+ "",
9778
+ `Harness: ${harness}`,
9779
+ ` server name: ${name}`,
9780
+ harnessResult.path ? ` config: ${harnessResult.path}` : ` registered: ${harnessTargetDesc(harness, io)}`,
9781
+ ` method: ${harnessResult.method}`,
9782
+ ` action: ${harnessResult.action}${harnessResult.detail ? ` (${harnessResult.detail})` : ""}`
9783
+ ];
9784
+ if (writeAlias && rcResult) {
9785
+ summary.push("", `Browser alias: ${resolvedAliasName} -> ${rcResult.rcPath}`, ` action: ${rcResult.action}`, ` line: ${line}`);
9786
+ if (!browserFound)
9787
+ summary.push(` note: ${browser} binary not found yet (see warning above)`);
9788
+ } else {
9789
+ summary.push("", "Browser alias: skipped (--no-alias).");
9790
+ }
9791
+ summary.push("", "Next steps:");
9792
+ if (writeAlias && rcResult) {
9793
+ summary.push(` 1. Load the alias into your shell: source ${rcResult.rcPath} (or open a new terminal)`, ` 2. Launch the browser with the debug port open: ${resolvedAliasName}`, ` 3. Restart / reconnect ${harness} so it picks up the "${name}" MCP server.`);
9794
+ if (browser === "firefox") {
9795
+ summary.push("", "Firefox note: the alias includes --marionette, which cdp-toolkit needs for its orphan", "auto-recovery path — don't remove it.");
9796
+ }
9797
+ } else {
9798
+ summary.push(` 1. Restart / reconnect ${harness} so it picks up the "${name}" MCP server.`);
9799
+ }
9800
+ summary.push("");
9801
+ write(`${summary.join(`
9802
+ `)}
9803
+ `);
9804
+ return 0;
9805
+ }
9806
+
8889
9807
  // src/cli.ts
8890
9808
  var USAGE = `cdp-toolkit: raw single-target CDP, 29-tool chrome-devtools-mcp parity, plus a Firefox backend over WebDriver BiDi.
8891
9809
 
@@ -9034,6 +9952,9 @@ function printToolHelp(name) {
9034
9952
  }
9035
9953
  async function main() {
9036
9954
  const rawArgv = process.argv.slice(2);
9955
+ if (rawArgv[0] === "install") {
9956
+ return await runInstaller(rawArgv.slice(1));
9957
+ }
9037
9958
  const { kind: browser, endpoint } = resolveBackend(rawArgv);
9038
9959
  const parsed = parseArgv(stripConnectFlag(stripBrowserFlag(rawArgv)));
9039
9960
  if (parsed.help) {