@xbrowser/cli 1.14.0 → 1.15.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.
@@ -27,7 +27,7 @@ var DEFAULT_STEALTH_CONFIG = {
27
27
  bezierCurvature: [0.35, 0.6],
28
28
  noiseAmplitude: 5.5,
29
29
  overshootRange: [6, 14],
30
- aimPause: [150, 400],
30
+ aimPause: [80, 280],
31
31
  pressDuration: [60, 140],
32
32
  releaseDrift: [0.8, 2.5],
33
33
  landingOffsetSmall: [0.3, 2.5],
@@ -48,6 +48,9 @@ var DEFAULT_STEALTH_CONFIG = {
48
48
  function rand(min, max) {
49
49
  return min + Math.random() * (max - min);
50
50
  }
51
+ function sleep(ms) {
52
+ return new Promise((resolve) => setTimeout(resolve, ms));
53
+ }
51
54
  function cosineEase(t) {
52
55
  return 0.5 - 0.5 * Math.cos(Math.PI * t);
53
56
  }
@@ -95,6 +98,18 @@ function landingOffset(width, height, config = DEFAULT_STEALTH_CONFIG) {
95
98
  const dy = rand(...range) * (Math.random() < 0.5 ? -1 : 1);
96
99
  return { dx, dy };
97
100
  }
101
+ function typingDelay(config = DEFAULT_STEALTH_CONFIG) {
102
+ const roll = Math.random();
103
+ const r = config.typingRhythm;
104
+ if (roll < r.pauseProb) return rand(...r.pauseRange);
105
+ if (roll < r.pauseProb + r.fastProb) return rand(...r.fastRange);
106
+ return rand(...r.normalRange);
107
+ }
108
+ function wheelDelta(step, config = DEFAULT_STEALTH_CONFIG) {
109
+ return Math.round(
110
+ config.wheelPeak * Math.exp(-step * config.wheelDecayRate) * rand(0.85, 1.15)
111
+ );
112
+ }
98
113
  var KEY_MAP = {};
99
114
  for (let i = 97; i <= 122; i++) {
100
115
  const ch = String.fromCharCode(i);
@@ -118,6 +133,7 @@ Object.assign(KEY_MAP, {
118
133
  function buildStealthInitScript() {
119
134
  return [
120
135
  "(function(){",
136
+ ' window.__xbStealthVer="57";',
121
137
  // 1. AEL event proxy
122
138
  " var o=EventTarget.prototype.addEventListener;",
123
139
  " var fc=new InputDeviceCapabilities({firesTouchEvents:false});",
@@ -280,6 +296,70 @@ function buildStealthInitScript() {
280
296
  ' var _pto=Object.getOwnPropertyDescriptor(Performance.prototype,"timeOrigin");',
281
297
  ' if(_pto&&_pto.get){Object.defineProperty(performance,"timeOrigin",{get:function(){return _pto.get.call(performance);},configurable:true});}',
282
298
  " }catch(e){}",
299
+ // 3f. getCoalescedEvents 合成(d47):真实鼠标 125Hz 采样被浏览器按帧合并,
300
+ // 快速移动时 pointermove.getCoalescedEvents() 返回 2~6 个事件(群内
301
+ // ≈8ms);CDP Input 逐事件派发不走合并管线,coalesced>1 恒为 0(结构性
302
+ // 暴露,间隔模拟救不了——实测 Chrome 帧对齐时 CDP 连发事件被直接丢弃
303
+ // 而非合并)。按 125Hz 物理插值合成 coalesced 群:期望样本数 = dt/8ms-1,
304
+ // 合成事件用真 PointerEvent 构造 + 实例级 isTrusted/timeStamp 遮蔽。
305
+ " try{",
306
+ " if(window.PointerEvent&&PointerEvent.prototype.getCoalescedEvents){",
307
+ " var _gce=PointerEvent.prototype.getCoalescedEvents;",
308
+ " var _lm=null;",
309
+ " var _gceH=function(){",
310
+ " var list=_gce.call(this);",
311
+ ' if(this.type!=="pointermove"||!this.isTrusted)return list;',
312
+ " var cur={x:this.clientX,y:this.clientY,ts:this.timeStamp};",
313
+ " var out=null;",
314
+ " if((!list||list.length<2)&&_lm){",
315
+ " var dt=cur.ts-_lm.ts,dx=cur.x-_lm.x,dy=cur.y-_lm.y;",
316
+ // dt 长(帧丢弃后)不代表群覆盖整个 dt —— 真实 coalesced 群只覆盖
317
+ // 最后一帧窗口(≤16.7ms,群内 ≈8ms=125Hz)。合成群从自身往回推 8ms
318
+ // 链,位移取末端占比(span/dt,dt 远大于 span 时位移趋零=丢弃后实况)。
319
+ " var expN=Math.floor(dt/8)-1;",
320
+ // dt<12ms(不足一帧+采样周期)时真实浏览器不会产生合并群 ——
321
+ // 单采样帧 coalesced=1,强行合成会出现 ~2ms 的超物理群内间隔。
322
+ " if(expN>0&&dt>=12&&Math.random()>0.15){",
323
+ " var n=Math.min(4,expN+(Math.random()<0.3?1:0));",
324
+ " var span=Math.min(dt,n*8.3);",
325
+ " var frac=Math.min(1,span/Math.max(dt,1));",
326
+ " out=[];",
327
+ " for(var i=1;i<=n;i++){",
328
+ // k=距自身的步数(含自身共 n+1 个事件,相邻恒 ≈8.3ms=125Hz 周期)
329
+ " var k=n+1-i;",
330
+ ' var ev=new PointerEvent("pointermove",{',
331
+ ' pointerId:this.pointerId,pointerType:"mouse",isPrimary:this.isPrimary,',
332
+ " clientX:cur.x-dx*frac*(k/(n+1))+(Math.random()-0.5)*1.5,",
333
+ " clientY:cur.y-dy*frac*(k/(n+1))+(Math.random()-0.5)*1.5,",
334
+ " screenX:this.screenX,screenY:this.screenY,",
335
+ " buttons:this.buttons,button:this.button,",
336
+ " bubbles:true,cancelable:true,composed:true,",
337
+ " width:this.width,height:this.height,pressure:this.pressure,",
338
+ " tiltX:this.tiltX,tiltY:this.tiltY,twist:this.twist});",
339
+ " var tsV=cur.ts-k*8.3-Math.random()*1.2;",
340
+ // isTrusted/timeStamp 是实例不可配置属性(defineProperty 抛
341
+ // "Cannot redefine"),改 Proxy 包装:getPrototypeOf/ownKeys 透传,
342
+ // instanceof 与属性枚举行为与真事件一致。IIFE 捕获本次循环的 tsV
343
+ // 快照 —— var 提升会让所有 Proxy 闭包共享最后一次赋值(实测四个
344
+ // 合成事件同时间戳、群内间隔塌到 ~2ms)。
345
+ " out.push((function(e2,t2){return new Proxy(e2,{get:function(t,p){",
346
+ ' if(p==="isTrusted")return true;',
347
+ ' if(p==="timeStamp")return t2;',
348
+ ' var v=Reflect.get(t,p);return typeof v==="function"?v.bind(t):v;',
349
+ " }});})(ev,tsV));",
350
+ " }",
351
+ " out.push(this);",
352
+ " }",
353
+ " }",
354
+ " _lm=cur;",
355
+ " return out||list;",
356
+ " };",
357
+ " PointerEvent.prototype.getCoalescedEvents=_gceH;",
358
+ // name 反查伪装:var _gceH=fn 的具名推断会暴露 hook(原生 name 是
359
+ // "getCoalescedEvents")——toString 白名单管不到 name 属性。
360
+ ' try{Object.defineProperty(_gceH,"name",{value:"getCoalescedEvents"});}catch(e){}',
361
+ " }",
362
+ " }catch(e){}",
283
363
  // 3d. Chrome object depth (d24): automation fakes usually only set
284
364
  // window.chrome = {}; deep checks hit app.run/runtime/csi/loadTimes.
285
365
  " try{",
@@ -314,6 +394,7 @@ function buildStealthInitScript() {
314
394
  ' if(this===HTMLCanvasElement.prototype.toDataURL)return"function toDataURL() { [native code] }";',
315
395
  ' if(this===AnalyserNode.prototype.getFloatFrequencyData)return"function getFloatFrequencyData() { [native code] }";',
316
396
  ' if(this===AudioBuffer.prototype.getChannelData)return"function getChannelData() { [native code] }";',
397
+ ' if(this===_gceH)return"function getCoalescedEvents() { [native code] }";',
317
398
  " return _ts.call(this);",
318
399
  " };",
319
400
  // 4. onclick prototype hijack (dual-stream consistency)
@@ -374,26 +455,26 @@ var XBMouseImpl = class {
374
455
  await this.send("Input.dispatchMouseEvent", { type: "mouseMoved", x: p.x, y: p.y, button: this._button });
375
456
  this._x = p.x;
376
457
  this._y = p.y;
377
- await sleep(p.delay);
458
+ await sleep2(p.delay);
378
459
  }
379
460
  this._x = tx;
380
461
  this._y = ty;
381
462
  if (_truncated) {
382
463
  await this.send("Input.dispatchMouseEvent", { type: "mouseMoved", x: tx, y: ty, button: this._button });
383
464
  }
384
- await sleep(rand(...DEFAULT_STEALTH_CONFIG.aimPause));
465
+ await sleep2(rand(...DEFAULT_STEALTH_CONFIG.aimPause));
385
466
  } else {
386
467
  await this.move(tx, ty);
387
468
  }
388
469
  await this.down({ button });
389
- await sleep(stealth ? rand(...DEFAULT_STEALTH_CONFIG.pressDuration) : opts.delay ?? 0);
470
+ await sleep2(stealth ? rand(...DEFAULT_STEALTH_CONFIG.pressDuration) : opts.delay ?? 0);
390
471
  const rx = stealth ? this._x + rand(...DEFAULT_STEALTH_CONFIG.releaseDrift) * (Math.random() < 0.5 ? -1 : 1) : this._x;
391
472
  const ry = stealth ? this._y + rand(...DEFAULT_STEALTH_CONFIG.releaseDrift) * (Math.random() < 0.5 ? -1 : 1) : this._y;
392
473
  this._x = rx;
393
474
  this._y = ry;
394
475
  await this.send("Input.dispatchMouseEvent", { type: "mouseReleased", x: rx, y: ry, button, clickCount: opts.clickCount ?? 1 });
395
476
  for (let i = 1; i < (opts.clickCount ?? 1); i++) {
396
- if (opts.delay) await sleep(opts.delay);
477
+ if (opts.delay) await sleep2(opts.delay);
397
478
  await this.down({ button });
398
479
  await this.up({ button });
399
480
  }
@@ -424,12 +505,14 @@ var XBMouseImpl = class {
424
505
  });
425
506
  }
426
507
  async move(x, y, opts = {}) {
508
+ const stealth = opts.stealth ?? process.env.XBROWSER_STEALTH !== "off";
427
509
  const steps = Math.max(1, opts.steps ?? 1);
428
510
  const fromX = this._x;
429
511
  const fromY = this._y;
430
512
  const dx = x - fromX;
431
513
  const dy = y - fromY;
432
514
  for (let i = 1; i <= steps; i++) {
515
+ if (stealth) await sleep2(rand(16, 28));
433
516
  const t = i / steps;
434
517
  this._x = fromX + dx * t;
435
518
  this._y = fromY + dy * t;
@@ -456,7 +539,7 @@ var XBMouseImpl = class {
456
539
  await this.conn.send(method, params, this.sessionId);
457
540
  }
458
541
  };
459
- function sleep(ms) {
542
+ function sleep2(ms) {
460
543
  return new Promise((resolve) => setTimeout(resolve, ms));
461
544
  }
462
545
 
@@ -486,17 +569,17 @@ var XBKeyboardImpl = class {
486
569
  await this.dispatchKeyEvent(downParams);
487
570
  if (mapping.text) {
488
571
  if (process.env.XBROWSER_STEALTH !== "off") {
489
- await sleep2(rand(...DEFAULT_STEALTH_CONFIG.keyPressDuration));
572
+ await sleep3(rand(...DEFAULT_STEALTH_CONFIG.keyPressDuration));
490
573
  }
491
574
  await this.dispatchKeyEvent({
492
575
  type: "char",
493
576
  text: mapping.text
494
577
  });
495
578
  }
496
- if (delay > 0) await sleep2(delay);
579
+ if (delay > 0) await sleep3(delay);
497
580
  else {
498
581
  if (process.env.XBROWSER_STEALTH !== "off") {
499
- await sleep2(rand(...DEFAULT_STEALTH_CONFIG.keyPressDuration));
582
+ await sleep3(rand(...DEFAULT_STEALTH_CONFIG.keyPressDuration));
500
583
  }
501
584
  }
502
585
  const upParams = {
@@ -542,45 +625,56 @@ var XBKeyboardImpl = class {
542
625
  const fixedDelay = opts.delay ?? 0;
543
626
  for (const char of text) {
544
627
  if (fixedDelay > 0) {
545
- await sleep2(fixedDelay);
628
+ await sleep3(fixedDelay);
546
629
  } else if (stealth) {
547
- const roll = Math.random();
548
- const cfg = DEFAULT_STEALTH_CONFIG.typingRhythm;
549
- if (roll < cfg.pauseProb) {
550
- await sleep2(rand(cfg.pauseRange[0], cfg.pauseRange[1]));
551
- } else if (roll < cfg.pauseProb + cfg.fastProb) {
552
- await sleep2(rand(cfg.fastRange[0], cfg.fastRange[1]));
553
- } else {
554
- await sleep2(rand(cfg.normalRange[0], cfg.normalRange[1]));
555
- }
556
- }
557
- const mapping = resolveKeyMapping(char);
558
- const downParams = {
559
- type: "rawKeyDown",
560
- key: mapping.key,
561
- code: mapping.code
562
- };
563
- if (mapping.text) {
564
- downParams.text = mapping.text;
565
- downParams.unmodifiedText = mapping.text;
566
- }
567
- if (mapping.keyCode) {
568
- downParams.windowsVirtualKeyCode = mapping.keyCode;
630
+ await sleep3(typingDelay());
569
631
  }
570
- await this.dispatchKeyEvent(downParams);
571
- if (mapping.text) {
572
- await this.dispatchKeyEvent({
573
- type: "char",
574
- text: mapping.text
575
- });
632
+ if (stealth && char.length === 1 && Math.random() < DEFAULT_STEALTH_CONFIG.typoProbability) {
633
+ const wrong = NEIGHBOR_KEYS[char];
634
+ if (wrong && wrong !== char) {
635
+ await this.dispatchKeySequence(resolveKeyMapping(wrong), stealth);
636
+ await sleep3(rand(120, 420));
637
+ await this.dispatchKeySequence(KEY_MAP2.Backspace, stealth);
638
+ }
576
639
  }
577
- await this.dispatchKeyEvent({
578
- type: "keyUp",
579
- key: mapping.key,
580
- code: mapping.code,
581
- ...mapping.keyCode ? { windowsVirtualKeyCode: mapping.keyCode } : {}
582
- });
640
+ await this.dispatchKeySequence(resolveKeyMapping(char), stealth);
641
+ }
642
+ }
643
+ /**
644
+ * Single-key event sequence: rawKeyDown (char) → keyUp.
645
+ * Shared by press() and type() — two separate implementations drifted
646
+ * apart once already (S60: type() lost the keyPressDuration that press()
647
+ * had; d50 caught it as 33/33 keys with down→up of 2ms).
648
+ */
649
+ async dispatchKeySequence(mapping, stealth) {
650
+ const downParams = {
651
+ type: "rawKeyDown",
652
+ key: mapping.key,
653
+ code: mapping.code
654
+ };
655
+ if (mapping.text) {
656
+ downParams.text = mapping.text;
657
+ downParams.unmodifiedText = mapping.text;
658
+ }
659
+ if (mapping.keyCode) {
660
+ downParams.windowsVirtualKeyCode = mapping.keyCode;
661
+ }
662
+ await this.dispatchKeyEvent(downParams);
663
+ if (mapping.text) {
664
+ await this.dispatchKeyEvent({ type: "char", text: mapping.text });
665
+ }
666
+ if (stealth) {
667
+ await sleep3(rand(...DEFAULT_STEALTH_CONFIG.keyPressDuration));
668
+ }
669
+ const upParams = {
670
+ type: "keyUp",
671
+ key: mapping.key,
672
+ code: mapping.code
673
+ };
674
+ if (mapping.keyCode) {
675
+ upParams.windowsVirtualKeyCode = mapping.keyCode;
583
676
  }
677
+ await this.dispatchKeyEvent(upParams);
584
678
  }
585
679
  async insertText(text) {
586
680
  await this.conn.send(
@@ -589,6 +683,35 @@ var XBKeyboardImpl = class {
589
683
  this.sessionId
590
684
  );
591
685
  }
686
+ /**
687
+ * Navigation key press using CDP type 'keyDown' (NOT rawKeyDown).
688
+ * rawKeyDown skips browser default actions — select option navigation,
689
+ * arrow-key scrolling etc. never fire under it (d53: ArrowDown on a
690
+ * focused <select> left the value unchanged). keyDown carries the
691
+ * default action.
692
+ */
693
+ async pressNav(key) {
694
+ const mapping = resolveKeyMapping(key);
695
+ const downParams = {
696
+ type: "keyDown",
697
+ key: mapping.key,
698
+ code: mapping.code
699
+ };
700
+ if (mapping.keyCode) {
701
+ downParams.windowsVirtualKeyCode = mapping.keyCode;
702
+ downParams.nativeVirtualKeyCode = mapping.keyCode;
703
+ }
704
+ await this.dispatchKeyEvent(downParams);
705
+ if (process.env.XBROWSER_STEALTH !== "off") {
706
+ await sleep3(rand(...DEFAULT_STEALTH_CONFIG.keyPressDuration));
707
+ }
708
+ await this.dispatchKeyEvent({
709
+ type: "keyUp",
710
+ key: mapping.key,
711
+ code: mapping.code,
712
+ ...mapping.keyCode ? { windowsVirtualKeyCode: mapping.keyCode } : {}
713
+ });
714
+ }
592
715
  async dispatchKeyEvent(params) {
593
716
  await this.conn.send("Input.dispatchKeyEvent", params, this.sessionId);
594
717
  }
@@ -643,7 +766,44 @@ var KEY_MAP2 = {
643
766
  F11: { key: "F11", code: "F11", keyCode: 122 },
644
767
  F12: { key: "F12", code: "F12", keyCode: 123 }
645
768
  };
646
- function sleep2(ms) {
769
+ var NEIGHBOR_KEYS = {
770
+ a: "s",
771
+ b: "v",
772
+ c: "x",
773
+ d: "f",
774
+ e: "r",
775
+ f: "g",
776
+ g: "h",
777
+ h: "j",
778
+ i: "o",
779
+ j: "k",
780
+ k: "l",
781
+ l: "k",
782
+ m: "n",
783
+ n: "m",
784
+ o: "p",
785
+ p: "o",
786
+ q: "w",
787
+ r: "t",
788
+ s: "d",
789
+ t: "y",
790
+ u: "i",
791
+ v: "b",
792
+ w: "e",
793
+ x: "c",
794
+ y: "u",
795
+ z: "x",
796
+ "1": "2",
797
+ "2": "3",
798
+ "3": "4",
799
+ "4": "5",
800
+ "5": "6",
801
+ "6": "7",
802
+ "7": "8",
803
+ "8": "9",
804
+ "9": "0"
805
+ };
806
+ function sleep3(ms) {
647
807
  return new Promise((resolve) => setTimeout(resolve, ms));
648
808
  }
649
809
 
@@ -858,23 +1018,13 @@ var XBLocatorImpl = class _XBLocatorImpl {
858
1018
  async press(key, opts = {}) {
859
1019
  await waitForActionable(this.page, this.selector, { timeout: opts.timeout });
860
1020
  await scrollIntoView(this.page, this.selector);
861
- await this.page.evaluate(`
862
- (function() {
863
- const el = ${this._q(this.selector)};
864
- if (el) el.focus();
865
- })()
866
- `);
1021
+ await this.click({ timeout: opts.timeout });
867
1022
  await this.page.keyboard.press(key);
868
1023
  }
869
1024
  async pressSequentially(text, opts = {}) {
870
1025
  await waitForActionable(this.page, this.selector, { timeout: opts.timeout });
871
1026
  await scrollIntoView(this.page, this.selector);
872
- await this.page.evaluate(`
873
- (function() {
874
- const el = ${this._q(this.selector)};
875
- if (el) el.focus();
876
- })()
877
- `);
1027
+ await this.click({ timeout: opts.timeout });
878
1028
  await this.page.keyboard.type(text, { delay: opts.delay });
879
1029
  }
880
1030
  async hover(opts = {}) {
@@ -929,11 +1079,36 @@ var XBLocatorImpl = class _XBLocatorImpl {
929
1079
  async selectOption(value) {
930
1080
  await waitForActionable(this.page, this.selector);
931
1081
  const values = Array.isArray(value) ? value : [value];
932
- const selected = await this.page.evaluate(`
1082
+ const info = await this.page.evaluate(`
933
1083
  (function() {
934
1084
  const el = ${this._q(this.selector)};
935
1085
  if (!el || el.tagName !== 'SELECT') throw new Error('Not a select element');
936
-
1086
+ const values = ${JSON.stringify(values)};
1087
+ let target = -1, targetValue = '';
1088
+ outer:
1089
+ for (let i = 0; i < el.options.length; i++) {
1090
+ const opt = el.options[i];
1091
+ for (const v of values) {
1092
+ const hit = typeof v === 'object'
1093
+ ? (v.label !== undefined ? opt.label === v.label
1094
+ : v.value !== undefined ? opt.value === v.value
1095
+ : opt.index === v.index)
1096
+ : (opt.value === v || opt.label === v);
1097
+ if (hit) { target = i; targetValue = opt.value; break outer; }
1098
+ }
1099
+ }
1100
+ return { cur: el.selectedIndex, target: target, targetValue: targetValue, multiple: el.multiple };
1101
+ })()
1102
+ `);
1103
+ if (info.target < 0) {
1104
+ throw new Error(`Option not found: ${JSON.stringify(values)}`);
1105
+ }
1106
+ if (!info.multiple) {
1107
+ await this.click({ timeout: 5e3 });
1108
+ }
1109
+ const selected = await this.page.evaluate(`
1110
+ (function() {
1111
+ const el = ${this._q(this.selector)};
937
1112
  const values = ${JSON.stringify(values)};
938
1113
  const selectedValues = [];
939
1114
 
@@ -4569,6 +4744,9 @@ async function launch(options = {}) {
4569
4744
  }
4570
4745
 
4571
4746
  export {
4747
+ rand,
4748
+ sleep,
4749
+ wheelDelta,
4572
4750
  XBMouseImpl,
4573
4751
  XBKeyboardImpl,
4574
4752
  waitForActionable,
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  createRuleEngine,
3
3
  launch
4
- } from "./chunk-UPAWITVM.js";
4
+ } from "./chunk-UXDGEDT7.js";
5
5
  import {
6
6
  errMsg
7
7
  } from "./chunk-GDKLH7ZY.js";
package/dist/cli.js CHANGED
@@ -56,10 +56,13 @@ import {
56
56
  getAllSessions,
57
57
  getBrowser,
58
58
  getSessionById,
59
+ rand,
59
60
  resolveLaunchOpts,
60
61
  saveSessionDiskMeta,
61
- setActivePage
62
- } from "./chunk-BTFKJD7Z.js";
62
+ setActivePage,
63
+ sleep,
64
+ wheelDelta
65
+ } from "./chunk-HFP4QCZL.js";
63
66
  import "./chunk-TNEN6VQ2.js";
64
67
  import {
65
68
  errMsg
@@ -352,7 +355,8 @@ var gotoCommand = registerCommand({
352
355
  parameters: z.object({
353
356
  url: z.string(),
354
357
  waitUntil: z.enum(["load", "domcontentloaded", "networkidle", "commit"]).optional(),
355
- timeout: z.number().optional()
358
+ timeout: z.number().optional(),
359
+ referrer: z.string().optional()
356
360
  }),
357
361
  result: z.object({
358
362
  url: z.string(),
@@ -373,7 +377,9 @@ var gotoCommand = registerCommand({
373
377
  try {
374
378
  response = await ctx.page.goto(url, {
375
379
  waitUntil: p.waitUntil || "domcontentloaded",
376
- ...p.timeout ? { timeout: p.timeout } : {}
380
+ ...p.timeout ? { timeout: p.timeout } : {},
381
+ // referrer(d54):模拟从源页面点链接进入 —— document.referrer 非空
382
+ ...p.referrer ? { referer: p.referrer } : {}
377
383
  });
378
384
  } catch (err) {
379
385
  const msg = err instanceof Error ? err.message : String(err);
@@ -900,21 +906,34 @@ var scrollCommand = registerCommand({
900
906
  }),
901
907
  handler: async (p, ctx) => {
902
908
  const distance = p.distance ?? 500;
903
- const deltas = {
904
- down: [0, distance],
905
- up: [0, -distance],
906
- right: [distance, 0],
907
- left: [-distance, 0]
908
- };
909
- const [dx, dy] = deltas[p.direction];
909
+ const sign = { down: 1, up: -1, right: 1, left: -1 };
910
+ const vertical = p.direction === "down" || p.direction === "up";
911
+ const s = sign[p.direction];
910
912
  if (p.selector) {
911
913
  const element = ctx.page.locator(p.selector).first();
912
914
  await element.evaluate((el, args) => {
913
915
  const [dxx, dyy] = args;
914
916
  el.scrollBy(dxx, dyy);
915
- }, [dx, dy]);
917
+ }, [vertical ? 0 : distance * s, vertical ? distance * s : 0]);
918
+ } else if (process.env.XBROWSER_STEALTH !== "off") {
919
+ let acc = 0;
920
+ let step = 0;
921
+ while (acc < distance && step < 60) {
922
+ const d = Math.min(wheelDelta(step), distance - acc);
923
+ if (d < 1) break;
924
+ await ctx.page.mouse.wheel(
925
+ vertical ? 0 : d * s,
926
+ vertical ? d * s : 0
927
+ );
928
+ acc += d;
929
+ step++;
930
+ await sleep(rand(16, 28));
931
+ }
916
932
  } else {
917
- await ctx.page.mouse.wheel(dx, dy);
933
+ await ctx.page.mouse.wheel(
934
+ vertical ? 0 : distance * s,
935
+ vertical ? distance * s : 0
936
+ );
918
937
  }
919
938
  return ok5({ direction: p.direction, distance });
920
939
  }
@@ -7523,7 +7542,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
7523
7542
  }
7524
7543
  let targetPageOverride = null;
7525
7544
  if (_target && extraOpts?.cdpEndpoint) {
7526
- const { findTargetPage } = await import("./browser-DI6UQN6Q.js");
7545
+ const { findTargetPage } = await import("./browser-JNT2I73V.js");
7527
7546
  targetPageOverride = await findTargetPage(extraOpts.cdpEndpoint, _target);
7528
7547
  if (!targetPageOverride) {
7529
7548
  return errorResult(`Target "${_target}" not found. Use 'xbrowser targets --cdp ${extraOpts.cdpEndpoint}' to list available pages.`);
@@ -7803,6 +7822,9 @@ async function executeChain(input, options) {
7803
7822
  for (const pipeline of pipelines) {
7804
7823
  const { type, pipeline: commands } = pipeline;
7805
7824
  for (const cmdStr of commands) {
7825
+ if (process.env.XBROWSER_CHAIN_PACE === "human" && results.length > 0) {
7826
+ await new Promise((r) => setTimeout(r, 800 + Math.random() * 1700));
7827
+ }
7806
7828
  const parts = splitCommand(cmdStr);
7807
7829
  if (parts.length === 0) continue;
7808
7830
  const cmdName = parts[0];
@@ -10173,12 +10195,33 @@ function normalizeSelector(input) {
10173
10195
 
10174
10196
  // src/cli/browser-routes.ts
10175
10197
  import { helpGenerator } from "@dyyz1993/xcli-core";
10198
+ function autoCompleteParams(cmdName, params, options) {
10199
+ try {
10200
+ const cmd = getCommand(cmdName);
10201
+ if (!cmd?.parameters) return params;
10202
+ const schema = asZodSchema(cmd.parameters);
10203
+ const shape = schema?.shape ?? schema?._def?.shape;
10204
+ if (!shape) return params;
10205
+ for (const key of Object.keys(shape)) {
10206
+ if (params[key] !== void 0) continue;
10207
+ const v = options[key];
10208
+ if (v === void 0 || v === true && key === "json" || key === "yaml") continue;
10209
+ if (typeof v === "string" && v === "") continue;
10210
+ params[key] = v;
10211
+ }
10212
+ return params;
10213
+ } catch {
10214
+ return params;
10215
+ }
10216
+ }
10176
10217
  function parseSelectorFlags(args, options) {
10177
- const selector = options.s || options.selector || options["selector"];
10178
- const value = options.v || options.value;
10218
+ const rawSelector = options.s ?? options.selector;
10219
+ const rawValue = options.v ?? options.value;
10220
+ const selector = typeof rawSelector === "string" ? normalizeSelector(rawSelector) : void 0;
10221
+ const value = typeof rawValue === "string" ? rawValue : void 0;
10179
10222
  const remaining = args.filter((a) => !a.startsWith("-"));
10180
10223
  return {
10181
- selector: selector ? normalizeSelector(selector) : void 0,
10224
+ selector,
10182
10225
  value,
10183
10226
  remaining
10184
10227
  };
@@ -10266,7 +10309,11 @@ async function handleBrowserCommand(command, args, options, sessionName, mode, c
10266
10309
  if (!sel || !txt)
10267
10310
  outputError("Usage: xbrowser type <selector> <text>\n xbrowser type -s <selector> -v <text>");
10268
10311
  cmdName = "type";
10269
- params = { selector: sel, text: txt };
10312
+ params = {
10313
+ selector: sel,
10314
+ text: txt,
10315
+ ...options.delay !== void 0 ? { delay: Number(options.delay) } : {}
10316
+ };
10270
10317
  break;
10271
10318
  }
10272
10319
  case "press": {
@@ -10408,7 +10455,13 @@ async function handleBrowserCommand(command, args, options, sessionName, mode, c
10408
10455
  outputError("Usage: xbrowser mouse <move|click|dblclick> <x> <y>\n xbrowser mouse --action <action> --x <x> --y <y>");
10409
10456
  }
10410
10457
  cmdName = "mouse";
10411
- params = { action, x, y, ...options.button ? { button: options.button } : {} };
10458
+ params = {
10459
+ action,
10460
+ x,
10461
+ y,
10462
+ ...options.button ? { button: options.button } : {},
10463
+ ...options.steps !== void 0 ? { steps: Number(options.steps) } : {}
10464
+ };
10412
10465
  break;
10413
10466
  }
10414
10467
  case "html":
@@ -10610,6 +10663,7 @@ async function handleBrowserCommand(command, args, options, sessionName, mode, c
10610
10663
  if (target) {
10611
10664
  params = { ...params, _target: target };
10612
10665
  }
10666
+ params = autoCompleteParams(cmdName, params, options);
10613
10667
  const tabIndex = options.tab;
10614
10668
  if (tabIndex !== void 0) {
10615
10669
  params = { ...params, _tabIndex: Number(tabIndex) };
@@ -13663,7 +13717,7 @@ Run "xbrowser ${command} ${subCommand} --help" to see available parameters.`
13663
13717
  const targetPage = pages[cmdTabIndex];
13664
13718
  await targetPage.bringToFront().catch(() => {
13665
13719
  });
13666
- const { setActivePage: setActivePage2 } = await import("./browser-DI6UQN6Q.js");
13720
+ const { setActivePage: setActivePage2 } = await import("./browser-JNT2I73V.js");
13667
13721
  setActivePage2(session, targetPage);
13668
13722
  }
13669
13723
  }
@@ -13939,7 +13993,7 @@ async function main() {
13939
13993
  const command = process.argv[2];
13940
13994
  const isLongRunning = command === "preview" || command === "serve";
13941
13995
  if (!isLongRunning) {
13942
- const { ensureProcessCanExit } = await import("./browser-DI6UQN6Q.js");
13996
+ const { ensureProcessCanExit } = await import("./browser-JNT2I73V.js");
13943
13997
  await ensureProcessCanExit().catch(() => {
13944
13998
  });
13945
13999
  process.exit(process.exitCode || exitCode);