@xbrowser/cli 1.15.0 → 1.17.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.
@@ -20,8 +20,8 @@ import {
20
20
  saveSessionDiskMeta,
21
21
  setActivePage,
22
22
  touchSession
23
- } from "./chunk-K5E4CWRV.js";
24
- import "./chunk-NTTFKS6T.js";
23
+ } from "./chunk-CE3L6HYV.js";
24
+ import "./chunk-3KUJOXUE.js";
25
25
  import "./chunk-TNEN6VQ2.js";
26
26
  import "./chunk-GDKLH7ZY.js";
27
27
  import "./chunk-A6LPGFAL.js";
@@ -20,7 +20,7 @@ import {
20
20
  saveSessionDiskMeta,
21
21
  setActivePage,
22
22
  touchSession
23
- } from "./chunk-HFP4QCZL.js";
23
+ } from "./chunk-3LVR44KG.js";
24
24
  import "./chunk-TNEN6VQ2.js";
25
25
  import "./chunk-GDKLH7ZY.js";
26
26
  import "./chunk-KFQGP6VL.js";
@@ -20,8 +20,8 @@ import {
20
20
  saveSessionDiskMeta,
21
21
  setActivePage,
22
22
  touchSession
23
- } from "./chunk-XQ4KOZ37.js";
24
- import "./chunk-UXDGEDT7.js";
23
+ } from "./chunk-UQUJVBXE.js";
24
+ import "./chunk-W4JETHAD.js";
25
25
  import "./chunk-3FWLW7FS.js";
26
26
  import "./chunk-TNEN6VQ2.js";
27
27
  import "./chunk-GDKLH7ZY.js";
@@ -14,7 +14,7 @@ import {
14
14
  scrollIntoView,
15
15
  waitForActionable,
16
16
  waitForNetworkIdle
17
- } from "./chunk-UXDGEDT7.js";
17
+ } from "./chunk-W4JETHAD.js";
18
18
  import "./chunk-3FWLW7FS.js";
19
19
  import {
20
20
  connectToCDP,
@@ -14,7 +14,7 @@ import {
14
14
  scrollIntoView,
15
15
  waitForActionable,
16
16
  waitForNetworkIdle
17
- } from "./chunk-NTTFKS6T.js";
17
+ } from "./chunk-3KUJOXUE.js";
18
18
  import {
19
19
  connectToCDP,
20
20
  findChrome,
@@ -515,6 +515,49 @@ var XBMouseImpl = class {
515
515
  this._x = x;
516
516
  this._y = y;
517
517
  }
518
+ /**
519
+ * Drag from the CURRENT cursor position to (x, y): press, traverse a
520
+ * bezier trajectory with the button held, release. Drives the REAL
521
+ * HTML5 DnD pipeline — field-verified (d59): this sequence fires
522
+ * dragstart → dragover… → drop → dragend, all isTrusted=true. No
523
+ * Input.dispatchDragEvent needed.
524
+ */
525
+ async drag(x, y, opts = {}) {
526
+ const stealth = process.env.XBROWSER_STEALTH !== "off";
527
+ await this.send("Input.dispatchMouseEvent", {
528
+ type: "mousePressed",
529
+ x: this._x,
530
+ y: this._y,
531
+ button: "left",
532
+ clickCount: 1
533
+ });
534
+ this._button = "left";
535
+ const steps = opts.steps ?? Math.max(10, Math.min(24, Math.round(Math.hypot(x - this._x, y - this._y) / 20)));
536
+ const fx = this._x, fy = this._y;
537
+ for (let i = 1; i <= steps; i++) {
538
+ if (stealth) await sleep(rand(16, 28));
539
+ const t = i / steps;
540
+ this._x = fx + (x - fx) * t;
541
+ this._y = fy + (y - fy) * t;
542
+ await this.send("Input.dispatchMouseEvent", {
543
+ type: "mouseMoved",
544
+ x: this._x,
545
+ y: this._y,
546
+ button: this._button
547
+ });
548
+ }
549
+ this._x = x;
550
+ this._y = y;
551
+ await sleep(rand(60, 140));
552
+ await this.send("Input.dispatchMouseEvent", {
553
+ type: "mouseReleased",
554
+ x,
555
+ y,
556
+ button: "left",
557
+ clickCount: 1
558
+ });
559
+ this._button = "none";
560
+ }
518
561
  async wheel(deltaX, deltaY) {
519
562
  await this.send("Input.dispatchMouseEvent", {
520
563
  type: "mouseWheel",
@@ -701,6 +744,26 @@ var XBKeyboardImpl = class {
701
744
  ...mapping.keyCode ? { windowsVirtualKeyCode: mapping.keyCode } : {}
702
745
  });
703
746
  }
747
+ /**
748
+ * Shortcut combo press (modifier+key) with explicit per-event modifiers
749
+ * bitmask — plain down(mod)+press(key) inserts the raw character because
750
+ * CDP modifiers are per-event fields, not session state (d56: Meta,v
751
+ * typed a literal 'v'). keyDown type carries the default action so the
752
+ * browser's shortcut dispatcher sees the combo (e.g. native paste).
753
+ */
754
+ async pressCombo(key, modifier) {
755
+ const MODBIT = { Alt: 1, Control: 2, Meta: 4, Shift: 8 };
756
+ const m = resolveKeyMapping(modifier);
757
+ const k = resolveKeyMapping(key);
758
+ const bits = MODBIT[modifier] ?? 0;
759
+ await this.dispatchKeyEvent({ type: "rawKeyDown", key: m.key, code: m.code, ...m.keyCode ? { windowsVirtualKeyCode: m.keyCode } : {}, modifiers: bits });
760
+ await this.dispatchKeyEvent({ type: "keyDown", key: k.key, code: k.code, ...k.keyCode ? { windowsVirtualKeyCode: k.keyCode } : {}, modifiers: bits });
761
+ if (process.env.XBROWSER_STEALTH !== "off") {
762
+ await sleep2(rand(...DEFAULT_STEALTH_CONFIG.keyPressDuration));
763
+ }
764
+ await this.dispatchKeyEvent({ type: "keyUp", key: k.key, code: k.code, ...k.keyCode ? { windowsVirtualKeyCode: k.keyCode } : {}, modifiers: bits });
765
+ await this.dispatchKeyEvent({ type: "keyUp", key: m.key, code: m.code, ...m.keyCode ? { windowsVirtualKeyCode: m.keyCode } : {}, modifiers: 0 });
766
+ }
704
767
  async dispatchKeyEvent(params) {
705
768
  await this.conn.send("Input.dispatchKeyEvent", params, this.sessionId);
706
769
  }
@@ -1084,6 +1147,18 @@ var XBLocatorImpl = class _XBLocatorImpl {
1084
1147
  await waitForActionable(this.page, this.selector, opts);
1085
1148
  await scrollIntoView(this.page, this.selector);
1086
1149
  await this.click({ ...opts });
1150
+ if (process.env.XBROWSER_STEALTH !== "off" && value.length >= 40 && process.env.XBROWSER_FILL_TYPE !== "type") {
1151
+ try {
1152
+ const { pasteViaClipboard, syntheticPaste } = await import("./clipboard-XRP54ENE.js");
1153
+ await pasteViaClipboard(this.page, value);
1154
+ const got = await this.page.evaluate(
1155
+ `(function(){ const el = ${this._q(this.selector)}; return el ? (el.value || '') : ''; })()`
1156
+ );
1157
+ if (got === value) return;
1158
+ if (await syntheticPaste(this.page, this._q(this.selector), value)) return;
1159
+ } catch {
1160
+ }
1161
+ }
1087
1162
  await this.page.keyboard.type(value, { stealth: true });
1088
1163
  return;
1089
1164
  await this.page.evaluate(`
@@ -1223,6 +1298,21 @@ var XBLocatorImpl = class _XBLocatorImpl {
1223
1298
  `);
1224
1299
  return selected;
1225
1300
  }
1301
+ async dragAndDrop(source, target) {
1302
+ const boxes = await this.page.evaluate(`
1303
+ (function() {
1304
+ const s = ${this._q(source)}, t = ${this._q(target)};
1305
+ if (!s || !t) return null;
1306
+ const c = (el) => { const r = el.getBoundingClientRect(); return { x: r.x + r.width / 2, y: r.y + r.height / 2 }; };
1307
+ return [c(s), c(t)];
1308
+ })()
1309
+ `);
1310
+ if (!boxes || !boxes[0] || !boxes[1]) {
1311
+ throw new Error(`dragAndDrop: element not found (${source} / ${target})`);
1312
+ }
1313
+ await this.page.mouse.move(boxes[0].x, boxes[0].y);
1314
+ await this.page.mouse.drag(boxes[1].x, boxes[1].y);
1315
+ }
1226
1316
  async screenshot(opts = {}) {
1227
1317
  await waitForActionable(this.page, this.selector);
1228
1318
  const box = await this.page.evaluate(`
@@ -525,6 +525,49 @@ var XBMouseImpl = class {
525
525
  this._x = x;
526
526
  this._y = y;
527
527
  }
528
+ /**
529
+ * Drag from the CURRENT cursor position to (x, y): press, traverse a
530
+ * bezier trajectory with the button held, release. Drives the REAL
531
+ * HTML5 DnD pipeline — field-verified (d59): this sequence fires
532
+ * dragstart → dragover… → drop → dragend, all isTrusted=true. No
533
+ * Input.dispatchDragEvent needed.
534
+ */
535
+ async drag(x, y, opts = {}) {
536
+ const stealth = process.env.XBROWSER_STEALTH !== "off";
537
+ await this.send("Input.dispatchMouseEvent", {
538
+ type: "mousePressed",
539
+ x: this._x,
540
+ y: this._y,
541
+ button: "left",
542
+ clickCount: 1
543
+ });
544
+ this._button = "left";
545
+ const steps = opts.steps ?? Math.max(10, Math.min(24, Math.round(Math.hypot(x - this._x, y - this._y) / 20)));
546
+ const fx = this._x, fy = this._y;
547
+ for (let i = 1; i <= steps; i++) {
548
+ if (stealth) await sleep2(rand(16, 28));
549
+ const t = i / steps;
550
+ this._x = fx + (x - fx) * t;
551
+ this._y = fy + (y - fy) * t;
552
+ await this.send("Input.dispatchMouseEvent", {
553
+ type: "mouseMoved",
554
+ x: this._x,
555
+ y: this._y,
556
+ button: this._button
557
+ });
558
+ }
559
+ this._x = x;
560
+ this._y = y;
561
+ await sleep2(rand(60, 140));
562
+ await this.send("Input.dispatchMouseEvent", {
563
+ type: "mouseReleased",
564
+ x,
565
+ y,
566
+ button: "left",
567
+ clickCount: 1
568
+ });
569
+ this._button = "none";
570
+ }
528
571
  async wheel(deltaX, deltaY) {
529
572
  await this.send("Input.dispatchMouseEvent", {
530
573
  type: "mouseWheel",
@@ -711,6 +754,26 @@ var XBKeyboardImpl = class {
711
754
  ...mapping.keyCode ? { windowsVirtualKeyCode: mapping.keyCode } : {}
712
755
  });
713
756
  }
757
+ /**
758
+ * Shortcut combo press (modifier+key) with explicit per-event modifiers
759
+ * bitmask — plain down(mod)+press(key) inserts the raw character because
760
+ * CDP modifiers are per-event fields, not session state (d56: Meta,v
761
+ * typed a literal 'v'). keyDown type carries the default action so the
762
+ * browser's shortcut dispatcher sees the combo (e.g. native paste).
763
+ */
764
+ async pressCombo(key, modifier) {
765
+ const MODBIT = { Alt: 1, Control: 2, Meta: 4, Shift: 8 };
766
+ const m = resolveKeyMapping(modifier);
767
+ const k = resolveKeyMapping(key);
768
+ const bits = MODBIT[modifier] ?? 0;
769
+ await this.dispatchKeyEvent({ type: "rawKeyDown", key: m.key, code: m.code, ...m.keyCode ? { windowsVirtualKeyCode: m.keyCode } : {}, modifiers: bits });
770
+ await this.dispatchKeyEvent({ type: "keyDown", key: k.key, code: k.code, ...k.keyCode ? { windowsVirtualKeyCode: k.keyCode } : {}, modifiers: bits });
771
+ if (process.env.XBROWSER_STEALTH !== "off") {
772
+ await sleep3(rand(...DEFAULT_STEALTH_CONFIG.keyPressDuration));
773
+ }
774
+ await this.dispatchKeyEvent({ type: "keyUp", key: k.key, code: k.code, ...k.keyCode ? { windowsVirtualKeyCode: k.keyCode } : {}, modifiers: bits });
775
+ await this.dispatchKeyEvent({ type: "keyUp", key: m.key, code: m.code, ...m.keyCode ? { windowsVirtualKeyCode: m.keyCode } : {}, modifiers: 0 });
776
+ }
714
777
  async dispatchKeyEvent(params) {
715
778
  await this.conn.send("Input.dispatchKeyEvent", params, this.sessionId);
716
779
  }
@@ -1094,6 +1157,18 @@ var XBLocatorImpl = class _XBLocatorImpl {
1094
1157
  await waitForActionable(this.page, this.selector, opts);
1095
1158
  await scrollIntoView(this.page, this.selector);
1096
1159
  await this.click({ ...opts });
1160
+ if (process.env.XBROWSER_STEALTH !== "off" && value.length >= 40 && process.env.XBROWSER_FILL_TYPE !== "type") {
1161
+ try {
1162
+ const { pasteViaClipboard, syntheticPaste } = await import("./clipboard-BOL2GP2E.js");
1163
+ await pasteViaClipboard(this.page, value);
1164
+ const got = await this.page.evaluate(
1165
+ `(function(){ const el = ${this._q(this.selector)}; return el ? (el.value || '') : ''; })()`
1166
+ );
1167
+ if (got === value) return;
1168
+ if (await syntheticPaste(this.page, this._q(this.selector), value)) return;
1169
+ } catch {
1170
+ }
1171
+ }
1097
1172
  await this.page.keyboard.type(value, { stealth: true });
1098
1173
  return;
1099
1174
  await this.page.evaluate(`
@@ -1233,6 +1308,21 @@ var XBLocatorImpl = class _XBLocatorImpl {
1233
1308
  `);
1234
1309
  return selected;
1235
1310
  }
1311
+ async dragAndDrop(source, target) {
1312
+ const boxes = await this.page.evaluate(`
1313
+ (function() {
1314
+ const s = ${this._q(source)}, t = ${this._q(target)};
1315
+ if (!s || !t) return null;
1316
+ const c = (el) => { const r = el.getBoundingClientRect(); return { x: r.x + r.width / 2, y: r.y + r.height / 2 }; };
1317
+ return [c(s), c(t)];
1318
+ })()
1319
+ `);
1320
+ if (!boxes || !boxes[0] || !boxes[1]) {
1321
+ throw new Error(`dragAndDrop: element not found (${source} / ${target})`);
1322
+ }
1323
+ await this.page.mouse.move(boxes[0].x, boxes[0].y);
1324
+ await this.page.mouse.drag(boxes[1].x, boxes[1].y);
1325
+ }
1236
1326
  async screenshot(opts = {}) {
1237
1327
  await waitForActionable(this.page, this.selector);
1238
1328
  const box = await this.page.evaluate(`
@@ -528,6 +528,49 @@ var XBMouseImpl = class {
528
528
  this._x = x;
529
529
  this._y = y;
530
530
  }
531
+ /**
532
+ * Drag from the CURRENT cursor position to (x, y): press, traverse a
533
+ * bezier trajectory with the button held, release. Drives the REAL
534
+ * HTML5 DnD pipeline — field-verified (d59): this sequence fires
535
+ * dragstart → dragover… → drop → dragend, all isTrusted=true. No
536
+ * Input.dispatchDragEvent needed.
537
+ */
538
+ async drag(x, y, opts = {}) {
539
+ const stealth = process.env.XBROWSER_STEALTH !== "off";
540
+ await this.send("Input.dispatchMouseEvent", {
541
+ type: "mousePressed",
542
+ x: this._x,
543
+ y: this._y,
544
+ button: "left",
545
+ clickCount: 1
546
+ });
547
+ this._button = "left";
548
+ const steps = opts.steps ?? Math.max(10, Math.min(24, Math.round(Math.hypot(x - this._x, y - this._y) / 20)));
549
+ const fx = this._x, fy = this._y;
550
+ for (let i = 1; i <= steps; i++) {
551
+ if (stealth) await sleep2(rand(16, 28));
552
+ const t = i / steps;
553
+ this._x = fx + (x - fx) * t;
554
+ this._y = fy + (y - fy) * t;
555
+ await this.send("Input.dispatchMouseEvent", {
556
+ type: "mouseMoved",
557
+ x: this._x,
558
+ y: this._y,
559
+ button: this._button
560
+ });
561
+ }
562
+ this._x = x;
563
+ this._y = y;
564
+ await sleep2(rand(60, 140));
565
+ await this.send("Input.dispatchMouseEvent", {
566
+ type: "mouseReleased",
567
+ x,
568
+ y,
569
+ button: "left",
570
+ clickCount: 1
571
+ });
572
+ this._button = "none";
573
+ }
531
574
  async wheel(deltaX, deltaY) {
532
575
  await this.send("Input.dispatchMouseEvent", {
533
576
  type: "mouseWheel",
@@ -714,6 +757,26 @@ var XBKeyboardImpl = class {
714
757
  ...mapping.keyCode ? { windowsVirtualKeyCode: mapping.keyCode } : {}
715
758
  });
716
759
  }
760
+ /**
761
+ * Shortcut combo press (modifier+key) with explicit per-event modifiers
762
+ * bitmask — plain down(mod)+press(key) inserts the raw character because
763
+ * CDP modifiers are per-event fields, not session state (d56: Meta,v
764
+ * typed a literal 'v'). keyDown type carries the default action so the
765
+ * browser's shortcut dispatcher sees the combo (e.g. native paste).
766
+ */
767
+ async pressCombo(key, modifier) {
768
+ const MODBIT = { Alt: 1, Control: 2, Meta: 4, Shift: 8 };
769
+ const m = resolveKeyMapping(modifier);
770
+ const k = resolveKeyMapping(key);
771
+ const bits = MODBIT[modifier] ?? 0;
772
+ await this.dispatchKeyEvent({ type: "rawKeyDown", key: m.key, code: m.code, ...m.keyCode ? { windowsVirtualKeyCode: m.keyCode } : {}, modifiers: bits });
773
+ await this.dispatchKeyEvent({ type: "keyDown", key: k.key, code: k.code, ...k.keyCode ? { windowsVirtualKeyCode: k.keyCode } : {}, modifiers: bits });
774
+ if (process.env.XBROWSER_STEALTH !== "off") {
775
+ await sleep3(rand(...DEFAULT_STEALTH_CONFIG.keyPressDuration));
776
+ }
777
+ await this.dispatchKeyEvent({ type: "keyUp", key: k.key, code: k.code, ...k.keyCode ? { windowsVirtualKeyCode: k.keyCode } : {}, modifiers: bits });
778
+ await this.dispatchKeyEvent({ type: "keyUp", key: m.key, code: m.code, ...m.keyCode ? { windowsVirtualKeyCode: m.keyCode } : {}, modifiers: 0 });
779
+ }
717
780
  async dispatchKeyEvent(params) {
718
781
  await this.conn.send("Input.dispatchKeyEvent", params, this.sessionId);
719
782
  }
@@ -1097,6 +1160,18 @@ var XBLocatorImpl = class _XBLocatorImpl {
1097
1160
  await waitForActionable(this.page, this.selector, opts);
1098
1161
  await scrollIntoView(this.page, this.selector);
1099
1162
  await this.click({ ...opts });
1163
+ if (process.env.XBROWSER_STEALTH !== "off" && value.length >= 40 && process.env.XBROWSER_FILL_TYPE !== "type") {
1164
+ try {
1165
+ const { pasteViaClipboard, syntheticPaste } = await import("./clipboard-BOL2GP2E.js");
1166
+ await pasteViaClipboard(this.page, value);
1167
+ const got = await this.page.evaluate(
1168
+ `(function(){ const el = ${this._q(this.selector)}; return el ? (el.value || '') : ''; })()`
1169
+ );
1170
+ if (got === value) return;
1171
+ if (await syntheticPaste(this.page, this._q(this.selector), value)) return;
1172
+ } catch {
1173
+ }
1174
+ }
1100
1175
  await this.page.keyboard.type(value, { stealth: true });
1101
1176
  return;
1102
1177
  await this.page.evaluate(`
@@ -1236,6 +1311,21 @@ var XBLocatorImpl = class _XBLocatorImpl {
1236
1311
  `);
1237
1312
  return selected;
1238
1313
  }
1314
+ async dragAndDrop(source, target) {
1315
+ const boxes = await this.page.evaluate(`
1316
+ (function() {
1317
+ const s = ${this._q(source)}, t = ${this._q(target)};
1318
+ if (!s || !t) return null;
1319
+ const c = (el) => { const r = el.getBoundingClientRect(); return { x: r.x + r.width / 2, y: r.y + r.height / 2 }; };
1320
+ return [c(s), c(t)];
1321
+ })()
1322
+ `);
1323
+ if (!boxes || !boxes[0] || !boxes[1]) {
1324
+ throw new Error(`dragAndDrop: element not found (${source} / ${target})`);
1325
+ }
1326
+ await this.page.mouse.move(boxes[0].x, boxes[0].y);
1327
+ await this.page.mouse.drag(boxes[1].x, boxes[1].y);
1328
+ }
1239
1329
  async screenshot(opts = {}) {
1240
1330
  await waitForActionable(this.page, this.selector);
1241
1331
  const box = await this.page.evaluate(`
@@ -5363,16 +5453,14 @@ process.on("exit", () => {
5363
5453
  async function getCDPTargets2(cdpEndpoint) {
5364
5454
  try {
5365
5455
  const ep = String(cdpEndpoint);
5366
- let host = "localhost";
5367
- let port = "9222";
5456
+ let url = "http://localhost:9222/json/list";
5368
5457
  if (ep.startsWith("http://") || ep.startsWith("https://")) {
5369
5458
  const u = new URL(ep);
5370
- host = u.hostname;
5371
- port = u.port || "9222";
5459
+ u.pathname = (u.pathname.replace(/\/+$/, "") || "") + "/json/list";
5460
+ url = u.toString();
5372
5461
  } else if (/^\d+$/.test(ep)) {
5373
- port = ep;
5462
+ url = `http://localhost:${ep}/json/list`;
5374
5463
  }
5375
- const url = `http://${host}:${port}/json/list`;
5376
5464
  const resp = await fetch(url);
5377
5465
  return await resp.json();
5378
5466
  } catch {
@@ -5843,20 +5931,48 @@ async function createSession(name, url, options) {
5843
5931
  (t) => t.url && t.url !== "about:blank" && !t.url.startsWith("chrome://") && !t.url.startsWith("chrome-untrusted://") && !t.url.startsWith("chrome-error://") && (url ? t.url.includes(new URL(url).hostname) : true)
5844
5932
  );
5845
5933
  if (matchTarget && matchTarget.url) {
5846
- targetPage = await context.newPage();
5847
- await targetPage.goto(matchTarget.url, { waitUntil: "domcontentloaded", timeout: 15e3 }).catch(() => {
5934
+ const existing2 = context.pages().find((p) => {
5935
+ try {
5936
+ return p.url() === matchTarget.url;
5937
+ } catch {
5938
+ return false;
5939
+ }
5848
5940
  });
5941
+ if (existing2) {
5942
+ targetPage = existing2;
5943
+ } else {
5944
+ targetPage = await context.newPage();
5945
+ await targetPage.goto(matchTarget.url, { waitUntil: "domcontentloaded", timeout: 15e3 }).catch(() => {
5946
+ });
5947
+ }
5849
5948
  }
5850
5949
  }
5851
5950
  if (!targetPage) {
5852
5951
  const pages = context.pages();
5853
- if (pages.length > 0) {
5952
+ if (pages.length > 1) {
5953
+ try {
5954
+ for (const p of pages) {
5955
+ const vis = await p.evaluate("document.visibilityState").catch(() => "hidden");
5956
+ if (vis === "visible") {
5957
+ targetPage = p;
5958
+ break;
5959
+ }
5960
+ }
5961
+ } catch {
5962
+ }
5963
+ }
5964
+ if (!targetPage && pages.length > 0) {
5854
5965
  targetPage = pages[0];
5855
- } else {
5966
+ }
5967
+ if (!targetPage) {
5856
5968
  targetPage = await context.newPage();
5857
5969
  }
5858
5970
  }
5859
5971
  page = targetPage;
5972
+ if (isCDP) {
5973
+ await Promise.resolve(targetPage.bringToFront?.()).catch(() => {
5974
+ });
5975
+ }
5860
5976
  } else {
5861
5977
  context = await b.newContext({ viewport: { width: 1920, height: 1080 } });
5862
5978
  page = await context.newPage();
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  launch
3
- } from "./chunk-NTTFKS6T.js";
3
+ } from "./chunk-3KUJOXUE.js";
4
4
  import {
5
5
  errMsg
6
6
  } from "./chunk-GDKLH7ZY.js";
@@ -161,16 +161,14 @@ process.on("exit", () => {
161
161
  async function getCDPTargets(cdpEndpoint) {
162
162
  try {
163
163
  const ep = String(cdpEndpoint);
164
- let host = "localhost";
165
- let port = "9222";
164
+ let url = "http://localhost:9222/json/list";
166
165
  if (ep.startsWith("http://") || ep.startsWith("https://")) {
167
166
  const u = new URL(ep);
168
- host = u.hostname;
169
- port = u.port || "9222";
167
+ u.pathname = (u.pathname.replace(/\/+$/, "") || "") + "/json/list";
168
+ url = u.toString();
170
169
  } else if (/^\d+$/.test(ep)) {
171
- port = ep;
170
+ url = `http://localhost:${ep}/json/list`;
172
171
  }
173
- const url = `http://${host}:${port}/json/list`;
174
172
  const resp = await fetch(url);
175
173
  return await resp.json();
176
174
  } catch {
@@ -641,20 +639,48 @@ async function createSession(name, url, options) {
641
639
  (t) => t.url && t.url !== "about:blank" && !t.url.startsWith("chrome://") && !t.url.startsWith("chrome-untrusted://") && !t.url.startsWith("chrome-error://") && (url ? t.url.includes(new URL(url).hostname) : true)
642
640
  );
643
641
  if (matchTarget && matchTarget.url) {
644
- targetPage = await context.newPage();
645
- await targetPage.goto(matchTarget.url, { waitUntil: "domcontentloaded", timeout: 15e3 }).catch(() => {
642
+ const existing2 = context.pages().find((p) => {
643
+ try {
644
+ return p.url() === matchTarget.url;
645
+ } catch {
646
+ return false;
647
+ }
646
648
  });
649
+ if (existing2) {
650
+ targetPage = existing2;
651
+ } else {
652
+ targetPage = await context.newPage();
653
+ await targetPage.goto(matchTarget.url, { waitUntil: "domcontentloaded", timeout: 15e3 }).catch(() => {
654
+ });
655
+ }
647
656
  }
648
657
  }
649
658
  if (!targetPage) {
650
659
  const pages = context.pages();
651
- if (pages.length > 0) {
660
+ if (pages.length > 1) {
661
+ try {
662
+ for (const p of pages) {
663
+ const vis = await p.evaluate("document.visibilityState").catch(() => "hidden");
664
+ if (vis === "visible") {
665
+ targetPage = p;
666
+ break;
667
+ }
668
+ }
669
+ } catch {
670
+ }
671
+ }
672
+ if (!targetPage && pages.length > 0) {
652
673
  targetPage = pages[0];
653
- } else {
674
+ }
675
+ if (!targetPage) {
654
676
  targetPage = await context.newPage();
655
677
  }
656
678
  }
657
679
  page = targetPage;
680
+ if (isCDP) {
681
+ await Promise.resolve(targetPage.bringToFront?.()).catch(() => {
682
+ });
683
+ }
658
684
  } else {
659
685
  context = await b.newContext({ viewport: { width: 1920, height: 1080 } });
660
686
  page = await context.newPage();
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  createRuleEngine,
3
3
  launch
4
- } from "./chunk-UXDGEDT7.js";
4
+ } from "./chunk-W4JETHAD.js";
5
5
  import {
6
6
  errMsg
7
7
  } from "./chunk-GDKLH7ZY.js";
@@ -588,16 +588,14 @@ process.on("exit", () => {
588
588
  async function getCDPTargets(cdpEndpoint) {
589
589
  try {
590
590
  const ep = String(cdpEndpoint);
591
- let host = "localhost";
592
- let port = "9222";
591
+ let url = "http://localhost:9222/json/list";
593
592
  if (ep.startsWith("http://") || ep.startsWith("https://")) {
594
593
  const u = new URL(ep);
595
- host = u.hostname;
596
- port = u.port || "9222";
594
+ u.pathname = (u.pathname.replace(/\/+$/, "") || "") + "/json/list";
595
+ url = u.toString();
597
596
  } else if (/^\d+$/.test(ep)) {
598
- port = ep;
597
+ url = `http://localhost:${ep}/json/list`;
599
598
  }
600
- const url = `http://${host}:${port}/json/list`;
601
599
  const resp = await fetch(url);
602
600
  return await resp.json();
603
601
  } catch {
@@ -1068,20 +1066,48 @@ async function createSession(name, url, options) {
1068
1066
  (t) => t.url && t.url !== "about:blank" && !t.url.startsWith("chrome://") && !t.url.startsWith("chrome-untrusted://") && !t.url.startsWith("chrome-error://") && (url ? t.url.includes(new URL(url).hostname) : true)
1069
1067
  );
1070
1068
  if (matchTarget && matchTarget.url) {
1071
- targetPage = await context.newPage();
1072
- await targetPage.goto(matchTarget.url, { waitUntil: "domcontentloaded", timeout: 15e3 }).catch(() => {
1069
+ const existing2 = context.pages().find((p) => {
1070
+ try {
1071
+ return p.url() === matchTarget.url;
1072
+ } catch {
1073
+ return false;
1074
+ }
1073
1075
  });
1076
+ if (existing2) {
1077
+ targetPage = existing2;
1078
+ } else {
1079
+ targetPage = await context.newPage();
1080
+ await targetPage.goto(matchTarget.url, { waitUntil: "domcontentloaded", timeout: 15e3 }).catch(() => {
1081
+ });
1082
+ }
1074
1083
  }
1075
1084
  }
1076
1085
  if (!targetPage) {
1077
1086
  const pages = context.pages();
1078
- if (pages.length > 0) {
1087
+ if (pages.length > 1) {
1088
+ try {
1089
+ for (const p of pages) {
1090
+ const vis = await p.evaluate("document.visibilityState").catch(() => "hidden");
1091
+ if (vis === "visible") {
1092
+ targetPage = p;
1093
+ break;
1094
+ }
1095
+ }
1096
+ } catch {
1097
+ }
1098
+ }
1099
+ if (!targetPage && pages.length > 0) {
1079
1100
  targetPage = pages[0];
1080
- } else {
1101
+ }
1102
+ if (!targetPage) {
1081
1103
  targetPage = await context.newPage();
1082
1104
  }
1083
1105
  }
1084
1106
  page = targetPage;
1107
+ if (isCDP) {
1108
+ await Promise.resolve(targetPage.bringToFront?.()).catch(() => {
1109
+ });
1110
+ }
1085
1111
  } else {
1086
1112
  context = await b.newContext({ viewport: { width: 1920, height: 1080 } });
1087
1113
  page = await context.newPage();
@@ -526,6 +526,49 @@ var XBMouseImpl = class {
526
526
  this._x = x;
527
527
  this._y = y;
528
528
  }
529
+ /**
530
+ * Drag from the CURRENT cursor position to (x, y): press, traverse a
531
+ * bezier trajectory with the button held, release. Drives the REAL
532
+ * HTML5 DnD pipeline — field-verified (d59): this sequence fires
533
+ * dragstart → dragover… → drop → dragend, all isTrusted=true. No
534
+ * Input.dispatchDragEvent needed.
535
+ */
536
+ async drag(x, y, opts = {}) {
537
+ const stealth = process.env.XBROWSER_STEALTH !== "off";
538
+ await this.send("Input.dispatchMouseEvent", {
539
+ type: "mousePressed",
540
+ x: this._x,
541
+ y: this._y,
542
+ button: "left",
543
+ clickCount: 1
544
+ });
545
+ this._button = "left";
546
+ const steps = opts.steps ?? Math.max(10, Math.min(24, Math.round(Math.hypot(x - this._x, y - this._y) / 20)));
547
+ const fx = this._x, fy = this._y;
548
+ for (let i = 1; i <= steps; i++) {
549
+ if (stealth) await sleep2(rand(16, 28));
550
+ const t = i / steps;
551
+ this._x = fx + (x - fx) * t;
552
+ this._y = fy + (y - fy) * t;
553
+ await this.send("Input.dispatchMouseEvent", {
554
+ type: "mouseMoved",
555
+ x: this._x,
556
+ y: this._y,
557
+ button: this._button
558
+ });
559
+ }
560
+ this._x = x;
561
+ this._y = y;
562
+ await sleep2(rand(60, 140));
563
+ await this.send("Input.dispatchMouseEvent", {
564
+ type: "mouseReleased",
565
+ x,
566
+ y,
567
+ button: "left",
568
+ clickCount: 1
569
+ });
570
+ this._button = "none";
571
+ }
529
572
  async wheel(deltaX, deltaY) {
530
573
  await this.send("Input.dispatchMouseEvent", {
531
574
  type: "mouseWheel",
@@ -712,6 +755,26 @@ var XBKeyboardImpl = class {
712
755
  ...mapping.keyCode ? { windowsVirtualKeyCode: mapping.keyCode } : {}
713
756
  });
714
757
  }
758
+ /**
759
+ * Shortcut combo press (modifier+key) with explicit per-event modifiers
760
+ * bitmask — plain down(mod)+press(key) inserts the raw character because
761
+ * CDP modifiers are per-event fields, not session state (d56: Meta,v
762
+ * typed a literal 'v'). keyDown type carries the default action so the
763
+ * browser's shortcut dispatcher sees the combo (e.g. native paste).
764
+ */
765
+ async pressCombo(key, modifier) {
766
+ const MODBIT = { Alt: 1, Control: 2, Meta: 4, Shift: 8 };
767
+ const m = resolveKeyMapping(modifier);
768
+ const k = resolveKeyMapping(key);
769
+ const bits = MODBIT[modifier] ?? 0;
770
+ await this.dispatchKeyEvent({ type: "rawKeyDown", key: m.key, code: m.code, ...m.keyCode ? { windowsVirtualKeyCode: m.keyCode } : {}, modifiers: bits });
771
+ await this.dispatchKeyEvent({ type: "keyDown", key: k.key, code: k.code, ...k.keyCode ? { windowsVirtualKeyCode: k.keyCode } : {}, modifiers: bits });
772
+ if (process.env.XBROWSER_STEALTH !== "off") {
773
+ await sleep3(rand(...DEFAULT_STEALTH_CONFIG.keyPressDuration));
774
+ }
775
+ await this.dispatchKeyEvent({ type: "keyUp", key: k.key, code: k.code, ...k.keyCode ? { windowsVirtualKeyCode: k.keyCode } : {}, modifiers: bits });
776
+ await this.dispatchKeyEvent({ type: "keyUp", key: m.key, code: m.code, ...m.keyCode ? { windowsVirtualKeyCode: m.keyCode } : {}, modifiers: 0 });
777
+ }
715
778
  async dispatchKeyEvent(params) {
716
779
  await this.conn.send("Input.dispatchKeyEvent", params, this.sessionId);
717
780
  }
@@ -993,6 +1056,18 @@ var XBLocatorImpl = class _XBLocatorImpl {
993
1056
  await waitForActionable(this.page, this.selector, opts);
994
1057
  await scrollIntoView(this.page, this.selector);
995
1058
  await this.click({ ...opts });
1059
+ if (process.env.XBROWSER_STEALTH !== "off" && value.length >= 40 && process.env.XBROWSER_FILL_TYPE !== "type") {
1060
+ try {
1061
+ const { pasteViaClipboard, syntheticPaste } = await import("./clipboard-BOL2GP2E.js");
1062
+ await pasteViaClipboard(this.page, value);
1063
+ const got = await this.page.evaluate(
1064
+ `(function(){ const el = ${this._q(this.selector)}; return el ? (el.value || '') : ''; })()`
1065
+ );
1066
+ if (got === value) return;
1067
+ if (await syntheticPaste(this.page, this._q(this.selector), value)) return;
1068
+ } catch {
1069
+ }
1070
+ }
996
1071
  await this.page.keyboard.type(value, { stealth: true });
997
1072
  return;
998
1073
  await this.page.evaluate(`
@@ -1132,6 +1207,21 @@ var XBLocatorImpl = class _XBLocatorImpl {
1132
1207
  `);
1133
1208
  return selected;
1134
1209
  }
1210
+ async dragAndDrop(source, target) {
1211
+ const boxes = await this.page.evaluate(`
1212
+ (function() {
1213
+ const s = ${this._q(source)}, t = ${this._q(target)};
1214
+ if (!s || !t) return null;
1215
+ const c = (el) => { const r = el.getBoundingClientRect(); return { x: r.x + r.width / 2, y: r.y + r.height / 2 }; };
1216
+ return [c(s), c(t)];
1217
+ })()
1218
+ `);
1219
+ if (!boxes || !boxes[0] || !boxes[1]) {
1220
+ throw new Error(`dragAndDrop: element not found (${source} / ${target})`);
1221
+ }
1222
+ await this.page.mouse.move(boxes[0].x, boxes[0].y);
1223
+ await this.page.mouse.drag(boxes[1].x, boxes[1].y);
1224
+ }
1135
1225
  async screenshot(opts = {}) {
1136
1226
  await waitForActionable(this.page, this.selector);
1137
1227
  const box = await this.page.evaluate(`
package/dist/cli.js CHANGED
@@ -26,6 +26,16 @@ import {
26
26
  import {
27
27
  buildViewerUrl
28
28
  } from "./chunk-3OD76SUE.js";
29
+ import {
30
+ NPM_REGISTRY_URL,
31
+ NPM_SCOPE,
32
+ getConfigValue,
33
+ getMarketplaceUrl,
34
+ loadConfig,
35
+ resolveNpmPackageWithFallback,
36
+ resolveScreenshotsDir,
37
+ setConfigValue
38
+ } from "./chunk-ZTHE5RBZ.js";
29
39
  import {
30
40
  getDaemonConfig,
31
41
  getDaemonProcessStatus,
@@ -62,7 +72,7 @@ import {
62
72
  setActivePage,
63
73
  sleep,
64
74
  wheelDelta
65
- } from "./chunk-HFP4QCZL.js";
75
+ } from "./chunk-3LVR44KG.js";
66
76
  import "./chunk-TNEN6VQ2.js";
67
77
  import {
68
78
  errMsg
@@ -71,16 +81,6 @@ import {
71
81
  detectAntiBot,
72
82
  formatDetectionMessage
73
83
  } from "./chunk-JKVUFP3G.js";
74
- import {
75
- NPM_REGISTRY_URL,
76
- NPM_SCOPE,
77
- getConfigValue,
78
- getMarketplaceUrl,
79
- loadConfig,
80
- resolveNpmPackageWithFallback,
81
- resolveScreenshotsDir,
82
- setConfigValue
83
- } from "./chunk-ZTHE5RBZ.js";
84
84
  import "./chunk-KFQGP6VL.js";
85
85
 
86
86
  // src/router.ts
@@ -947,14 +947,14 @@ var mouseCommand = registerCommand({
947
947
  description: "Control the mouse (move, click, etc.)",
948
948
  scope: "page",
949
949
  parameters: z6.object({
950
- action: z6.enum(["move", "down", "up", "click", "dblclick"]),
950
+ action: z6.enum(["move", "down", "up", "click", "dblclick", "drag"]),
951
951
  x: z6.coerce.number(),
952
952
  y: z6.coerce.number(),
953
953
  button: z6.enum(["left", "right", "middle"]).optional(),
954
954
  steps: z6.coerce.number().optional()
955
955
  }),
956
956
  result: z6.object({
957
- action: z6.enum(["move", "down", "up", "click", "dblclick"]),
957
+ action: z6.enum(["move", "down", "up", "click", "dblclick", "drag"]),
958
958
  x: z6.number(),
959
959
  y: z6.number()
960
960
  }),
@@ -978,6 +978,9 @@ var mouseCommand = registerCommand({
978
978
  case "dblclick":
979
979
  await ctx.page.mouse.dblclick(p.x, p.y, { button });
980
980
  break;
981
+ case "drag":
982
+ await ctx.page.mouse.drag(p.x, p.y, p.steps ? { steps: p.steps } : {});
983
+ break;
981
984
  }
982
985
  return ok6({ action: p.action, x: p.x, y: p.y });
983
986
  }
@@ -7542,7 +7545,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
7542
7545
  }
7543
7546
  let targetPageOverride = null;
7544
7547
  if (_target && extraOpts?.cdpEndpoint) {
7545
- const { findTargetPage } = await import("./browser-JNT2I73V.js");
7548
+ const { findTargetPage } = await import("./browser-HY7UXUQV.js");
7546
7549
  targetPageOverride = await findTargetPage(extraOpts.cdpEndpoint, _target);
7547
7550
  if (!targetPageOverride) {
7548
7551
  return errorResult(`Target "${_target}" not found. Use 'xbrowser targets --cdp ${extraOpts.cdpEndpoint}' to list available pages.`);
@@ -7756,7 +7759,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
7756
7759
  const errorMessage = errMsg(err);
7757
7760
  if (session?.page && process.env.XBROWSER_RECOVERY && !extraOpts?._recoveryAttempted) {
7758
7761
  try {
7759
- const { attemptRecovery } = await import("./recovery-FTZGV6VY.js");
7762
+ const { attemptRecovery } = await import("./recovery-NXC35EQN.js");
7760
7763
  const recovery = await attemptRecovery(
7761
7764
  session.page,
7762
7765
  sessionName,
@@ -13717,7 +13720,7 @@ Run "xbrowser ${command} ${subCommand} --help" to see available parameters.`
13717
13720
  const targetPage = pages[cmdTabIndex];
13718
13721
  await targetPage.bringToFront().catch(() => {
13719
13722
  });
13720
- const { setActivePage: setActivePage2 } = await import("./browser-JNT2I73V.js");
13723
+ const { setActivePage: setActivePage2 } = await import("./browser-HY7UXUQV.js");
13721
13724
  setActivePage2(session, targetPage);
13722
13725
  }
13723
13726
  }
@@ -13993,7 +13996,7 @@ async function main() {
13993
13996
  const command = process.argv[2];
13994
13997
  const isLongRunning = command === "preview" || command === "serve";
13995
13998
  if (!isLongRunning) {
13996
- const { ensureProcessCanExit } = await import("./browser-JNT2I73V.js");
13999
+ const { ensureProcessCanExit } = await import("./browser-HY7UXUQV.js");
13997
14000
  await ensureProcessCanExit().catch(() => {
13998
14001
  });
13999
14002
  process.exit(process.exitCode || exitCode);
@@ -0,0 +1,58 @@
1
+ import "./chunk-KFQGP6VL.js";
2
+
3
+ // src/utils/clipboard.ts
4
+ import { exec, execSync } from "child_process";
5
+ function writeClipboard(text) {
6
+ const plat = process.platform;
7
+ if (plat === "darwin") {
8
+ const p = exec("pbcopy");
9
+ if (!p.stdin) throw new Error("pbcopy stdin unavailable");
10
+ p.stdin.write(text);
11
+ p.stdin.end();
12
+ execSync("sleep 0.05");
13
+ } else if (plat === "linux") {
14
+ execSync("command -v xclip >/dev/null 2>&1 && echo ok", { stdio: "ignore" });
15
+ const p = exec("xclip -selection clipboard -in");
16
+ if (!p.stdin) throw new Error("xclip stdin unavailable");
17
+ p.stdin.write(text);
18
+ p.stdin.end();
19
+ execSync("sleep 0.05");
20
+ } else if (plat === "win32") {
21
+ const p = exec("clip");
22
+ if (!p.stdin) throw new Error("clip stdin unavailable");
23
+ p.stdin.write(text);
24
+ p.stdin.end();
25
+ execSync("timeout /t 1 /nobreak >nul");
26
+ } else {
27
+ throw new Error(`Unsupported platform for clipboard: ${plat}`);
28
+ }
29
+ }
30
+ async function pasteViaClipboard(page, text) {
31
+ writeClipboard(text);
32
+ const kb = page.keyboard;
33
+ const mod = process.platform === "darwin" ? "Meta" : "Control";
34
+ await kb.pressCombo("v", mod);
35
+ await new Promise((r) => setTimeout(r, 150));
36
+ }
37
+ async function syntheticPaste(page, selector, text) {
38
+ const result = await page.evaluate(`
39
+ (function() {
40
+ const el = ${"{SELECTOR}"};
41
+ if (!el) return false;
42
+ el.focus();
43
+ if (el.value) { el.select(); document.execCommand('delete'); }
44
+ try {
45
+ const dt = new DataTransfer();
46
+ dt.setData('text/plain', ${JSON.stringify(text)});
47
+ el.dispatchEvent(new ClipboardEvent('paste', { clipboardData: dt, bubbles: true, cancelable: true }));
48
+ } catch (e) { /* ClipboardEvent ctor guard */ }
49
+ const ok = document.execCommand('insertText', false, ${JSON.stringify(text)});
50
+ return ok === true && (el.value || '') === ${JSON.stringify(text)};
51
+ })()
52
+ `.replace("{SELECTOR}", selector));
53
+ return result === true;
54
+ }
55
+ export {
56
+ pasteViaClipboard,
57
+ syntheticPaste
58
+ };
@@ -0,0 +1,58 @@
1
+ import "./chunk-3RG5ZIWI.js";
2
+
3
+ // src/utils/clipboard.ts
4
+ import { exec, execSync } from "child_process";
5
+ function writeClipboard(text) {
6
+ const plat = process.platform;
7
+ if (plat === "darwin") {
8
+ const p = exec("pbcopy");
9
+ if (!p.stdin) throw new Error("pbcopy stdin unavailable");
10
+ p.stdin.write(text);
11
+ p.stdin.end();
12
+ execSync("sleep 0.05");
13
+ } else if (plat === "linux") {
14
+ execSync("command -v xclip >/dev/null 2>&1 && echo ok", { stdio: "ignore" });
15
+ const p = exec("xclip -selection clipboard -in");
16
+ if (!p.stdin) throw new Error("xclip stdin unavailable");
17
+ p.stdin.write(text);
18
+ p.stdin.end();
19
+ execSync("sleep 0.05");
20
+ } else if (plat === "win32") {
21
+ const p = exec("clip");
22
+ if (!p.stdin) throw new Error("clip stdin unavailable");
23
+ p.stdin.write(text);
24
+ p.stdin.end();
25
+ execSync("timeout /t 1 /nobreak >nul");
26
+ } else {
27
+ throw new Error(`Unsupported platform for clipboard: ${plat}`);
28
+ }
29
+ }
30
+ async function pasteViaClipboard(page, text) {
31
+ writeClipboard(text);
32
+ const kb = page.keyboard;
33
+ const mod = process.platform === "darwin" ? "Meta" : "Control";
34
+ await kb.pressCombo("v", mod);
35
+ await new Promise((r) => setTimeout(r, 150));
36
+ }
37
+ async function syntheticPaste(page, selector, text) {
38
+ const result = await page.evaluate(`
39
+ (function() {
40
+ const el = ${"{SELECTOR}"};
41
+ if (!el) return false;
42
+ el.focus();
43
+ if (el.value) { el.select(); document.execCommand('delete'); }
44
+ try {
45
+ const dt = new DataTransfer();
46
+ dt.setData('text/plain', ${JSON.stringify(text)});
47
+ el.dispatchEvent(new ClipboardEvent('paste', { clipboardData: dt, bubbles: true, cancelable: true }));
48
+ } catch (e) { /* ClipboardEvent ctor guard */ }
49
+ const ok = document.execCommand('insertText', false, ${JSON.stringify(text)});
50
+ return ok === true && (el.value || '') === ${JSON.stringify(text)};
51
+ })()
52
+ `.replace("{SELECTOR}", selector));
53
+ return result === true;
54
+ }
55
+ export {
56
+ pasteViaClipboard,
57
+ syntheticPaste
58
+ };
@@ -7,6 +7,10 @@ import {
7
7
  import {
8
8
  buildViewerUrl
9
9
  } from "./chunk-TZPKFUBT.js";
10
+ import {
11
+ ScreencastCapturer,
12
+ resolveScreenshotsDir
13
+ } from "./chunk-HBMEFSTB.js";
10
14
  import "./chunk-CG3D3CHY.js";
11
15
  import {
12
16
  commandLogStore,
@@ -30,13 +34,13 @@ import {
30
34
  resolveLaunchOpts,
31
35
  saveSessionDiskMeta,
32
36
  setActivePage
33
- } from "./chunk-XQ4KOZ37.js";
37
+ } from "./chunk-UQUJVBXE.js";
34
38
  import {
35
39
  createRuleEngine,
36
40
  rand,
37
41
  sleep,
38
42
  wheelDelta
39
- } from "./chunk-UXDGEDT7.js";
43
+ } from "./chunk-W4JETHAD.js";
40
44
  import {
41
45
  queryJS
42
46
  } from "./chunk-3FWLW7FS.js";
@@ -48,10 +52,6 @@ import {
48
52
  detectAntiBot,
49
53
  formatDetectionMessage
50
54
  } from "./chunk-JKVUFP3G.js";
51
- import {
52
- ScreencastCapturer,
53
- resolveScreenshotsDir
54
- } from "./chunk-HBMEFSTB.js";
55
55
  import "./chunk-KFQGP6VL.js";
56
56
 
57
57
  // src/daemon/daemon-main.ts
@@ -907,14 +907,14 @@ var mouseCommand = registerCommand({
907
907
  description: "Control the mouse (move, click, etc.)",
908
908
  scope: "page",
909
909
  parameters: z6.object({
910
- action: z6.enum(["move", "down", "up", "click", "dblclick"]),
910
+ action: z6.enum(["move", "down", "up", "click", "dblclick", "drag"]),
911
911
  x: z6.coerce.number(),
912
912
  y: z6.coerce.number(),
913
913
  button: z6.enum(["left", "right", "middle"]).optional(),
914
914
  steps: z6.coerce.number().optional()
915
915
  }),
916
916
  result: z6.object({
917
- action: z6.enum(["move", "down", "up", "click", "dblclick"]),
917
+ action: z6.enum(["move", "down", "up", "click", "dblclick", "drag"]),
918
918
  x: z6.number(),
919
919
  y: z6.number()
920
920
  }),
@@ -938,6 +938,9 @@ var mouseCommand = registerCommand({
938
938
  case "dblclick":
939
939
  await ctx.page.mouse.dblclick(p.x, p.y, { button });
940
940
  break;
941
+ case "drag":
942
+ await ctx.page.mouse.drag(p.x, p.y, p.steps ? { steps: p.steps } : {});
943
+ break;
941
944
  }
942
945
  return ok6({ action: p.action, x: p.x, y: p.y });
943
946
  }
@@ -7060,7 +7063,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
7060
7063
  }
7061
7064
  let targetPageOverride = null;
7062
7065
  if (_target && extraOpts?.cdpEndpoint) {
7063
- const { findTargetPage } = await import("./browser-YYMKZWTU.js");
7066
+ const { findTargetPage } = await import("./browser-ZVNDI4TR.js");
7064
7067
  targetPageOverride = await findTargetPage(extraOpts.cdpEndpoint, _target);
7065
7068
  if (!targetPageOverride) {
7066
7069
  return errorResult(`Target "${_target}" not found. Use 'xbrowser targets --cdp ${extraOpts.cdpEndpoint}' to list available pages.`);
@@ -7274,7 +7277,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
7274
7277
  const errorMessage = errMsg(err);
7275
7278
  if (session?.page && process.env.XBROWSER_RECOVERY && !extraOpts?._recoveryAttempted) {
7276
7279
  try {
7277
- const { attemptRecovery } = await import("./recovery-Z3RDZENA.js");
7280
+ const { attemptRecovery } = await import("./recovery-MDWAVXE4.js");
7278
7281
  const recovery = await attemptRecovery(
7279
7282
  session.page,
7280
7283
  sessionName,
@@ -8314,7 +8317,7 @@ function createRPCHandler() {
8314
8317
  return result;
8315
8318
  } catch (err) {
8316
8319
  const errorMessage = errMsg(err);
8317
- const { attemptRecovery } = await import("./recovery-Z3RDZENA.js");
8320
+ const { attemptRecovery } = await import("./recovery-MDWAVXE4.js");
8318
8321
  const recovery = await attemptRecovery(
8319
8322
  session?.page,
8320
8323
  sessionName,
@@ -8879,7 +8882,7 @@ function createRPCHandler() {
8879
8882
  if (isNewFormat) {
8880
8883
  try {
8881
8884
  const replayErrors = [];
8882
- const { SessionReplayer } = await import("./session-replayer-DKHXF3DZ.js");
8885
+ const { SessionReplayer } = await import("./session-replayer-NV65I6OQ.js");
8883
8886
  const replayer = new SessionReplayer({
8884
8887
  page: session.page,
8885
8888
  stepDelay: slowMo * 500,
package/dist/index.d.ts CHANGED
@@ -311,6 +311,10 @@ interface XBMouse {
311
311
  steps?: number;
312
312
  }): Promise<void>;
313
313
  wheel(deltaX: number, deltaY: number): Promise<void>;
314
+ /** Drag from current position to (x,y) — drives the real HTML5 DnD pipeline */
315
+ drag(x: number, y: number, opts?: {
316
+ steps?: number;
317
+ }): Promise<void>;
314
318
  }
315
319
  interface XBKeyboard {
316
320
  press(key: string, opts?: {
@@ -318,6 +322,8 @@ interface XBKeyboard {
318
322
  }): Promise<void>;
319
323
  /** Navigation key with CDP 'keyDown' type (carries browser default actions) */
320
324
  pressNav(key: string): Promise<void>;
325
+ /** Shortcut combo (modifier+key) with explicit per-event modifiers bitmask */
326
+ pressCombo(key: string, modifier: 'Meta' | 'Control' | 'Alt' | 'Shift'): Promise<void>;
321
327
  down(key: string): Promise<void>;
322
328
  up(key: string): Promise<void>;
323
329
  type(text: string, opts?: {
package/dist/index.js CHANGED
@@ -1,3 +1,24 @@
1
+ import {
2
+ filterRecording,
3
+ parseExcludeTypes
4
+ } from "./chunk-ANVL2ID2.js";
5
+ import {
6
+ closeAllSessions,
7
+ closeEphemeralContext,
8
+ closeSessionByName,
9
+ createEphemeralContext,
10
+ createSession,
11
+ destroyBrowser,
12
+ findOrRestoreSession,
13
+ findSession,
14
+ getAllSessions,
15
+ getBrowser,
16
+ getSessionById,
17
+ resetForTesting,
18
+ resolveLaunchOpts,
19
+ saveSessionDiskMeta,
20
+ setActivePage
21
+ } from "./chunk-CE3L6HYV.js";
1
22
  import {
2
23
  detectAntiBot,
3
24
  formatDetectionMessage
@@ -61,43 +82,11 @@ import {
61
82
  extractRecording,
62
83
  printExtractSummary
63
84
  } from "./chunk-X46HDAOT.js";
64
- import {
65
- filterRecording,
66
- parseExcludeTypes
67
- } from "./chunk-ANVL2ID2.js";
68
- import {
69
- SessionRecorder
70
- } from "./chunk-NODRQGOK.js";
71
- import {
72
- addKnownIssue,
73
- getKnowledgePath,
74
- init_site_knowledge,
75
- listSiteKnowledge,
76
- readSiteKnowledge,
77
- readSiteKnowledgeMarkdown
78
- } from "./chunk-OZKD3W4X.js";
79
- import {
80
- closeAllSessions,
81
- closeEphemeralContext,
82
- closeSessionByName,
83
- createEphemeralContext,
84
- createSession,
85
- destroyBrowser,
86
- findOrRestoreSession,
87
- findSession,
88
- getAllSessions,
89
- getBrowser,
90
- getSessionById,
91
- resetForTesting,
92
- resolveLaunchOpts,
93
- saveSessionDiskMeta,
94
- setActivePage
95
- } from "./chunk-K5E4CWRV.js";
96
85
  import {
97
86
  rand,
98
87
  sleep,
99
88
  wheelDelta
100
- } from "./chunk-NTTFKS6T.js";
89
+ } from "./chunk-3KUJOXUE.js";
101
90
  import "./chunk-TNEN6VQ2.js";
102
91
  import {
103
92
  errMsg
@@ -118,6 +107,17 @@ import {
118
107
  networkAnomalyRule,
119
108
  pageLifecycleRule
120
109
  } from "./chunk-H2A5JUK5.js";
110
+ import {
111
+ SessionRecorder
112
+ } from "./chunk-NODRQGOK.js";
113
+ import {
114
+ addKnownIssue,
115
+ getKnowledgePath,
116
+ init_site_knowledge,
117
+ listSiteKnowledge,
118
+ readSiteKnowledge,
119
+ readSiteKnowledgeMarkdown
120
+ } from "./chunk-OZKD3W4X.js";
121
121
  import {
122
122
  __require
123
123
  } from "./chunk-KFQGP6VL.js";
@@ -981,14 +981,14 @@ var mouseCommand = registerCommand({
981
981
  description: "Control the mouse (move, click, etc.)",
982
982
  scope: "page",
983
983
  parameters: z6.object({
984
- action: z6.enum(["move", "down", "up", "click", "dblclick"]),
984
+ action: z6.enum(["move", "down", "up", "click", "dblclick", "drag"]),
985
985
  x: z6.coerce.number(),
986
986
  y: z6.coerce.number(),
987
987
  button: z6.enum(["left", "right", "middle"]).optional(),
988
988
  steps: z6.coerce.number().optional()
989
989
  }),
990
990
  result: z6.object({
991
- action: z6.enum(["move", "down", "up", "click", "dblclick"]),
991
+ action: z6.enum(["move", "down", "up", "click", "dblclick", "drag"]),
992
992
  x: z6.number(),
993
993
  y: z6.number()
994
994
  }),
@@ -1012,6 +1012,9 @@ var mouseCommand = registerCommand({
1012
1012
  case "dblclick":
1013
1013
  await ctx.page.mouse.dblclick(p.x, p.y, { button });
1014
1014
  break;
1015
+ case "drag":
1016
+ await ctx.page.mouse.drag(p.x, p.y, p.steps ? { steps: p.steps } : {});
1017
+ break;
1015
1018
  }
1016
1019
  return ok6({ action: p.action, x: p.x, y: p.y });
1017
1020
  }
@@ -7893,7 +7896,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
7893
7896
  }
7894
7897
  let targetPageOverride = null;
7895
7898
  if (_target && extraOpts?.cdpEndpoint) {
7896
- const { findTargetPage } = await import("./browser-4FKHBUBJ.js");
7899
+ const { findTargetPage } = await import("./browser-GPRLH6P6.js");
7897
7900
  targetPageOverride = await findTargetPage(extraOpts.cdpEndpoint, _target);
7898
7901
  if (!targetPageOverride) {
7899
7902
  return errorResult(`Target "${_target}" not found. Use 'xbrowser targets --cdp ${extraOpts.cdpEndpoint}' to list available pages.`);
@@ -14106,7 +14109,7 @@ Run "xbrowser ${command} ${subCommand} --help" to see available parameters.`
14106
14109
  const targetPage = pages[cmdTabIndex];
14107
14110
  await targetPage.bringToFront().catch(() => {
14108
14111
  });
14109
- const { setActivePage: setActivePage2 } = await import("./browser-4FKHBUBJ.js");
14112
+ const { setActivePage: setActivePage2 } = await import("./browser-GPRLH6P6.js");
14110
14113
  setActivePage2(session, targetPage);
14111
14114
  }
14112
14115
  }
@@ -17279,7 +17282,7 @@ var DataCollector = class {
17279
17282
  return results;
17280
17283
  }
17281
17284
  async createBrowserContext() {
17282
- const { launch } = await import("./cdp-driver-QX72T7SU.js");
17285
+ const { launch } = await import("./cdp-driver-GMDP4KDN.js");
17283
17286
  const { browser } = await launch({
17284
17287
  headless: true,
17285
17288
  args: ["--no-sandbox", "--disable-setuid-sandbox"]
@@ -34,7 +34,7 @@ var SessionReplayer = class {
34
34
  if (this.opts.page) {
35
35
  this.page = this.opts.page;
36
36
  } else if (this.opts.cdpUrl) {
37
- const { launch } = await import("./cdp-driver-7RW4NY2X.js");
37
+ const { launch } = await import("./cdp-driver-FIAUJTBZ.js");
38
38
  const { browser } = await launch({ cdpEndpoint: this.opts.cdpUrl });
39
39
  let contexts = browser.contexts();
40
40
  for (let i = 0; i < 10 && contexts.length === 0; i++) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xbrowser/cli",
3
- "version": "1.15.0",
3
+ "version": "1.17.0",
4
4
  "description": "Browser automation CLI for web scraping, headless browsing, SEO analysis, and AI agent workflows. A command-line alternative to Playwright, Puppeteer, and Selenium.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,11 +1,11 @@
1
- import {
2
- buildViewerUrl
3
- } from "./chunk-TZPKFUBT.js";
4
- import "./chunk-CG3D3CHY.js";
5
1
  import {
6
2
  HumanInteractionManager
7
3
  } from "./chunk-OMU63E6J.js";
4
+ import {
5
+ buildViewerUrl
6
+ } from "./chunk-TZPKFUBT.js";
8
7
  import "./chunk-HBMEFSTB.js";
8
+ import "./chunk-CG3D3CHY.js";
9
9
  import "./chunk-KFQGP6VL.js";
10
10
 
11
11
  // src/recovery.ts
@@ -1,11 +1,11 @@
1
- import {
2
- buildViewerUrl
3
- } from "./chunk-3OD76SUE.js";
4
- import "./chunk-Q2DFJQGS.js";
5
1
  import {
6
2
  HumanInteractionManager
7
3
  } from "./chunk-TEXCXIBW.js";
4
+ import {
5
+ buildViewerUrl
6
+ } from "./chunk-3OD76SUE.js";
8
7
  import "./chunk-ZTHE5RBZ.js";
8
+ import "./chunk-Q2DFJQGS.js";
9
9
  import "./chunk-KFQGP6VL.js";
10
10
 
11
11
  // src/recovery.ts