@xbrowser/cli 1.25.2 → 1.26.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,10 +20,10 @@ import {
20
20
  saveSessionDiskMeta,
21
21
  setActivePage,
22
22
  touchSession
23
- } from "./chunk-NNI5RT4A.js";
24
- import "./chunk-U6GFDVNI.js";
23
+ } from "./chunk-KECYFWJO.js";
24
+ import "./chunk-J3HXZVSN.js";
25
25
  import "./chunk-R3F4K2SB.js";
26
- import "./chunk-MYH6FIBZ.js";
26
+ import "./chunk-SEEWE5SJ.js";
27
27
  import "./chunk-GDKLH7ZY.js";
28
28
  import "./chunk-WYETQ5C7.js";
29
29
  import "./chunk-75DNUI6M.js";
@@ -20,9 +20,9 @@ import {
20
20
  saveSessionDiskMeta,
21
21
  setActivePage,
22
22
  touchSession
23
- } from "./chunk-2EEAXSBA.js";
23
+ } from "./chunk-HSYRAV3H.js";
24
24
  import "./chunk-R3F4K2SB.js";
25
- import "./chunk-MYH6FIBZ.js";
25
+ import "./chunk-SEEWE5SJ.js";
26
26
  import "./chunk-GDKLH7ZY.js";
27
27
  import "./chunk-KFQGP6VL.js";
28
28
  export {
@@ -20,11 +20,11 @@ import {
20
20
  saveSessionDiskMeta,
21
21
  setActivePage,
22
22
  touchSession
23
- } from "./chunk-ZOYM7QMS.js";
24
- import "./chunk-CGNHGAIJ.js";
23
+ } from "./chunk-3PUOISKE.js";
24
+ import "./chunk-ME5FO5YV.js";
25
25
  import "./chunk-A7NEA4PA.js";
26
26
  import "./chunk-R3F4K2SB.js";
27
- import "./chunk-MYH6FIBZ.js";
27
+ import "./chunk-SEEWE5SJ.js";
28
28
  import "./chunk-GDKLH7ZY.js";
29
29
  import "./chunk-KFQGP6VL.js";
30
30
  export {
@@ -14,7 +14,7 @@ import {
14
14
  scrollIntoView,
15
15
  waitForActionable,
16
16
  waitForNetworkIdle
17
- } from "./chunk-CGNHGAIJ.js";
17
+ } from "./chunk-ME5FO5YV.js";
18
18
  import "./chunk-A7NEA4PA.js";
19
19
  import "./chunk-R3F4K2SB.js";
20
20
  import {
@@ -23,7 +23,7 @@ import {
23
23
  getCDPTargets,
24
24
  killChrome,
25
25
  launchChrome
26
- } from "./chunk-MYH6FIBZ.js";
26
+ } from "./chunk-SEEWE5SJ.js";
27
27
  import "./chunk-GDKLH7ZY.js";
28
28
  import "./chunk-KFQGP6VL.js";
29
29
  export {
@@ -14,7 +14,7 @@ import {
14
14
  scrollIntoView,
15
15
  waitForActionable,
16
16
  waitForNetworkIdle
17
- } from "./chunk-U6GFDVNI.js";
17
+ } from "./chunk-J3HXZVSN.js";
18
18
  import "./chunk-R3F4K2SB.js";
19
19
  import {
20
20
  connectToCDP,
@@ -22,7 +22,7 @@ import {
22
22
  getCDPTargets,
23
23
  killChrome,
24
24
  launchChrome
25
- } from "./chunk-MYH6FIBZ.js";
25
+ } from "./chunk-SEEWE5SJ.js";
26
26
  import "./chunk-GDKLH7ZY.js";
27
27
  import "./chunk-75DNUI6M.js";
28
28
  import "./chunk-KFQGP6VL.js";
@@ -13,7 +13,7 @@ import {
13
13
  getCDPTargets,
14
14
  killChrome,
15
15
  launchChrome
16
- } from "./chunk-VCVDILH6.js";
16
+ } from "./chunk-T2AI24A5.js";
17
17
  import {
18
18
  __require
19
19
  } from "./chunk-3RG5ZIWI.js";
@@ -3417,7 +3417,7 @@ var XBBrowserImpl = class {
3417
3417
  this._exitHandler = null;
3418
3418
  }
3419
3419
  if (this.childProcess) {
3420
- const { killChrome: killChrome2 } = await import("./launcher-LUOMMGZC.js");
3420
+ const { killChrome: killChrome2 } = await import("./launcher-SJ44X25U.js");
3421
3421
  await killChrome2(this.childProcess, this.tmpDir);
3422
3422
  }
3423
3423
  await this.conn.close();
@@ -3587,7 +3587,7 @@ var XBBrowserImpl = class {
3587
3587
  if (pageTargets.length === 0 && httpFallbackUrl) {
3588
3588
  console.log(`[discoverContexts] Target.getTargets returned ${targetInfos.length} targets (0 page type). Falling back to HTTP /json/list at ${httpFallbackUrl}`);
3589
3589
  try {
3590
- const { getCDPTargets: getCDPTargets2 } = await import("./launcher-LUOMMGZC.js");
3590
+ const { getCDPTargets: getCDPTargets2 } = await import("./launcher-SJ44X25U.js");
3591
3591
  const httpPages = await getCDPTargets2(httpFallbackUrl);
3592
3592
  console.log(`[discoverContexts] HTTP /json/list returned ${httpPages.length} pages`);
3593
3593
  for (const p of httpPages) {
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  createRuleEngine,
3
3
  launch
4
- } from "./chunk-CGNHGAIJ.js";
4
+ } from "./chunk-ME5FO5YV.js";
5
5
  import {
6
6
  errMsg
7
7
  } from "./chunk-GDKLH7ZY.js";
@@ -9,7 +9,7 @@ import {
9
9
  resolveLaunchOpts,
10
10
  saveSessionDiskMeta,
11
11
  setActivePage
12
- } from "./chunk-NNI5RT4A.js";
12
+ } from "./chunk-KECYFWJO.js";
13
13
  import {
14
14
  detectAntiBot,
15
15
  formatDetectionMessage
@@ -867,10 +867,14 @@ var clickCommand = registerCommand({
867
867
  targetIdsBefore = /* @__PURE__ */ new Set();
868
868
  }
869
869
  const pagePromise = new Promise((resolve3) => {
870
+ const waitMs = (() => {
871
+ const v = parseInt(process.env.XBROWSER_NEW_TAB_WAIT ?? "250", 10);
872
+ return Number.isFinite(v) && v > 0 ? v : 250;
873
+ })();
870
874
  const timer = setTimeout(() => {
871
875
  ctx.browserContext.off("page", handler);
872
876
  resolve3(void 0);
873
- }, 3e3);
877
+ }, waitMs);
874
878
  const handler = (page2) => {
875
879
  if (targetIdsBefore.has(targetIdOf(page2))) return;
876
880
  try {
@@ -8295,7 +8299,10 @@ function buildSnapshot(elements) {
8295
8299
  }
8296
8300
 
8297
8301
  // src/tips/index.ts
8298
- var DEBOUNCE_MS = 500;
8302
+ function tipsDebounceMs() {
8303
+ const v = parseInt(process.env.XBROWSER_TIPS_DEBOUNCE ?? "120", 10);
8304
+ return Number.isFinite(v) && v > 0 ? v : 0;
8305
+ }
8299
8306
  var SKIP_DETECT_COMMANDS = /* @__PURE__ */ new Set([
8300
8307
  "goto",
8301
8308
  // 导航命令不需要检测
@@ -8387,7 +8394,9 @@ var TipsManager = class {
8387
8394
  }
8388
8395
  }
8389
8396
  debounce() {
8390
- return new Promise((resolve3) => setTimeout(resolve3, DEBOUNCE_MS));
8397
+ const ms = tipsDebounceMs();
8398
+ if (ms <= 0) return Promise.resolve();
8399
+ return new Promise((resolve3) => setTimeout(resolve3, ms));
8391
8400
  }
8392
8401
  formatTips(tips) {
8393
8402
  return tips.map((tip) => {
@@ -8636,7 +8645,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
8636
8645
  }
8637
8646
  let targetPageOverride = null;
8638
8647
  if (_target && extraOpts?.cdpEndpoint) {
8639
- const { findTargetPage } = await import("./browser-L3TUDYY3.js");
8648
+ const { findTargetPage } = await import("./browser-4IYIHZ6R.js");
8640
8649
  targetPageOverride = await findTargetPage(extraOpts.cdpEndpoint, _target);
8641
8650
  if (!targetPageOverride) {
8642
8651
  return errorResult(`Target "${_target}" not found. Use 'xbrowser targets --cdp ${extraOpts.cdpEndpoint}' to list available pages.`);
@@ -20,7 +20,7 @@ import {
20
20
  resolveLaunchOpts,
21
21
  saveSessionDiskMeta,
22
22
  setActivePage
23
- } from "./chunk-2EEAXSBA.js";
23
+ } from "./chunk-HSYRAV3H.js";
24
24
  import {
25
25
  rand,
26
26
  sleep,
@@ -515,10 +515,14 @@ var clickCommand = registerCommand({
515
515
  targetIdsBefore = /* @__PURE__ */ new Set();
516
516
  }
517
517
  const pagePromise = new Promise((resolve3) => {
518
+ const waitMs = (() => {
519
+ const v = parseInt(process.env.XBROWSER_NEW_TAB_WAIT ?? "250", 10);
520
+ return Number.isFinite(v) && v > 0 ? v : 250;
521
+ })();
518
522
  const timer = setTimeout(() => {
519
523
  ctx.browserContext.off("page", handler);
520
524
  resolve3(void 0);
521
- }, 3e3);
525
+ }, waitMs);
522
526
  const handler = (page2) => {
523
527
  if (targetIdsBefore.has(targetIdOf(page2))) return;
524
528
  try {
@@ -7981,7 +7985,10 @@ function buildSnapshot(elements) {
7981
7985
  }
7982
7986
 
7983
7987
  // src/tips/index.ts
7984
- var DEBOUNCE_MS = 500;
7988
+ function tipsDebounceMs() {
7989
+ const v = parseInt(process.env.XBROWSER_TIPS_DEBOUNCE ?? "120", 10);
7990
+ return Number.isFinite(v) && v > 0 ? v : 0;
7991
+ }
7985
7992
  var SKIP_DETECT_COMMANDS = /* @__PURE__ */ new Set([
7986
7993
  "goto",
7987
7994
  // 导航命令不需要检测
@@ -8073,7 +8080,9 @@ var TipsManager = class {
8073
8080
  }
8074
8081
  }
8075
8082
  debounce() {
8076
- return new Promise((resolve3) => setTimeout(resolve3, DEBOUNCE_MS));
8083
+ const ms = tipsDebounceMs();
8084
+ if (ms <= 0) return Promise.resolve();
8085
+ return new Promise((resolve3) => setTimeout(resolve3, ms));
8077
8086
  }
8078
8087
  formatTips(tips) {
8079
8088
  return tips.map((tip) => {
@@ -8319,7 +8328,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
8319
8328
  }
8320
8329
  let targetPageOverride = null;
8321
8330
  if (_target && extraOpts?.cdpEndpoint) {
8322
- const { findTargetPage } = await import("./browser-33QQWEDI.js");
8331
+ const { findTargetPage } = await import("./browser-NYYCR4O7.js");
8323
8332
  targetPageOverride = await findTargetPage(extraOpts.cdpEndpoint, _target);
8324
8333
  if (!targetPageOverride) {
8325
8334
  return errorResult(`Target "${_target}" not found. Use 'xbrowser targets --cdp ${extraOpts.cdpEndpoint}' to list available pages.`);
@@ -9,7 +9,7 @@ import {
9
9
  import {
10
10
  connectToCDP,
11
11
  launchChrome
12
- } from "./chunk-MYH6FIBZ.js";
12
+ } from "./chunk-SEEWE5SJ.js";
13
13
  import {
14
14
  errMsg
15
15
  } from "./chunk-GDKLH7ZY.js";
@@ -3422,7 +3422,7 @@ var XBBrowserImpl = class {
3422
3422
  this._exitHandler = null;
3423
3423
  }
3424
3424
  if (this.childProcess) {
3425
- const { killChrome: killChrome2 } = await import("./launcher-IK2FKW6T.js");
3425
+ const { killChrome: killChrome2 } = await import("./launcher-XHKIC4GP.js");
3426
3426
  await killChrome2(this.childProcess, this.tmpDir);
3427
3427
  }
3428
3428
  await this.conn.close();
@@ -3592,7 +3592,7 @@ var XBBrowserImpl = class {
3592
3592
  if (pageTargets.length === 0 && httpFallbackUrl) {
3593
3593
  console.log(`[discoverContexts] Target.getTargets returned ${targetInfos.length} targets (0 page type). Falling back to HTTP /json/list at ${httpFallbackUrl}`);
3594
3594
  try {
3595
- const { getCDPTargets: getCDPTargets3 } = await import("./launcher-IK2FKW6T.js");
3595
+ const { getCDPTargets: getCDPTargets3 } = await import("./launcher-XHKIC4GP.js");
3596
3596
  const httpPages = await getCDPTargets3(httpFallbackUrl);
3597
3597
  console.log(`[discoverContexts] HTTP /json/list returned ${httpPages.length} pages`);
3598
3598
  for (const p of httpPages) {
@@ -9,7 +9,7 @@ import {
9
9
  import {
10
10
  connectToCDP,
11
11
  launchChrome
12
- } from "./chunk-MYH6FIBZ.js";
12
+ } from "./chunk-SEEWE5SJ.js";
13
13
  import {
14
14
  errMsg
15
15
  } from "./chunk-GDKLH7ZY.js";
@@ -3419,7 +3419,7 @@ var XBBrowserImpl = class {
3419
3419
  this._exitHandler = null;
3420
3420
  }
3421
3421
  if (this.childProcess) {
3422
- const { killChrome: killChrome2 } = await import("./launcher-IK2FKW6T.js");
3422
+ const { killChrome: killChrome2 } = await import("./launcher-XHKIC4GP.js");
3423
3423
  await killChrome2(this.childProcess, this.tmpDir);
3424
3424
  }
3425
3425
  await this.conn.close();
@@ -3589,7 +3589,7 @@ var XBBrowserImpl = class {
3589
3589
  if (pageTargets.length === 0 && httpFallbackUrl) {
3590
3590
  console.log(`[discoverContexts] Target.getTargets returned ${targetInfos.length} targets (0 page type). Falling back to HTTP /json/list at ${httpFallbackUrl}`);
3591
3591
  try {
3592
- const { getCDPTargets: getCDPTargets2 } = await import("./launcher-IK2FKW6T.js");
3592
+ const { getCDPTargets: getCDPTargets2 } = await import("./launcher-XHKIC4GP.js");
3593
3593
  const httpPages = await getCDPTargets2(httpFallbackUrl);
3594
3594
  console.log(`[discoverContexts] HTTP /json/list returned ${httpPages.length} pages`);
3595
3595
  for (const p of httpPages) {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  launch
3
- } from "./chunk-U6GFDVNI.js";
3
+ } from "./chunk-J3HXZVSN.js";
4
4
  import {
5
5
  errMsg
6
6
  } from "./chunk-GDKLH7ZY.js";
@@ -13,7 +13,7 @@ import {
13
13
  import {
14
14
  connectToCDP,
15
15
  launchChrome
16
- } from "./chunk-MYH6FIBZ.js";
16
+ } from "./chunk-SEEWE5SJ.js";
17
17
  import {
18
18
  errMsg
19
19
  } from "./chunk-GDKLH7ZY.js";
@@ -3318,7 +3318,7 @@ var XBBrowserImpl = class {
3318
3318
  this._exitHandler = null;
3319
3319
  }
3320
3320
  if (this.childProcess) {
3321
- const { killChrome: killChrome2 } = await import("./launcher-IK2FKW6T.js");
3321
+ const { killChrome: killChrome2 } = await import("./launcher-XHKIC4GP.js");
3322
3322
  await killChrome2(this.childProcess, this.tmpDir);
3323
3323
  }
3324
3324
  await this.conn.close();
@@ -3488,7 +3488,7 @@ var XBBrowserImpl = class {
3488
3488
  if (pageTargets.length === 0 && httpFallbackUrl) {
3489
3489
  console.log(`[discoverContexts] Target.getTargets returned ${targetInfos.length} targets (0 page type). Falling back to HTTP /json/list at ${httpFallbackUrl}`);
3490
3490
  try {
3491
- const { getCDPTargets: getCDPTargets2 } = await import("./launcher-IK2FKW6T.js");
3491
+ const { getCDPTargets: getCDPTargets2 } = await import("./launcher-XHKIC4GP.js");
3492
3492
  const httpPages = await getCDPTargets2(httpFallbackUrl);
3493
3493
  console.log(`[discoverContexts] HTTP /json/list returned ${httpPages.length} pages`);
3494
3494
  for (const p of httpPages) {
@@ -52,7 +52,11 @@ var DEFAULT_ARGS = [
52
52
  ];
53
53
  var ANTI_DETECT_ARGS = [
54
54
  "--disable-blink-features=AutomationControlled",
55
- "--disable-features=IsolateOrigins,site-per-process"
55
+ "--disable-features=IsolateOrigins,site-per-process",
56
+ // headless UA 清洗(browserscan 实测 2026-09-14):headless 默认 UA 含
57
+ // "HeadlessChrome"——这是反爬系统最高频检测面之一。必须在 launch 级覆盖
58
+ // (stealth init-script 只能覆盖 JS 层 navigator.userAgent,HTTP header 盖不住)
59
+ "--user-agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36"
56
60
  ];
57
61
  function findChrome() {
58
62
  const platform = process.platform;
@@ -56,7 +56,11 @@ var DEFAULT_ARGS = [
56
56
  ];
57
57
  var ANTI_DETECT_ARGS = [
58
58
  "--disable-blink-features=AutomationControlled",
59
- "--disable-features=IsolateOrigins,site-per-process"
59
+ "--disable-features=IsolateOrigins,site-per-process",
60
+ // headless UA 清洗(browserscan 实测 2026-09-14):headless 默认 UA 含
61
+ // "HeadlessChrome"——这是反爬系统最高频检测面之一。必须在 launch 级覆盖
62
+ // (stealth init-script 只能覆盖 JS 层 navigator.userAgent,HTTP header 盖不住)
63
+ "--user-agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36"
60
64
  ];
61
65
  function findChrome() {
62
66
  const platform = process.platform;
package/dist/cli.js CHANGED
@@ -17,7 +17,7 @@ import {
17
17
  loadHooks,
18
18
  parsePluginParams,
19
19
  readJsonFile
20
- } from "./chunk-PO27V5UH.js";
20
+ } from "./chunk-HDFQRDBC.js";
21
21
  import "./chunk-JKVUFP3G.js";
22
22
  import {
23
23
  forwardCommandLog,
@@ -81,9 +81,9 @@ import {
81
81
  findSession,
82
82
  getAllSessions,
83
83
  saveSessionDiskMeta
84
- } from "./chunk-2EEAXSBA.js";
84
+ } from "./chunk-HSYRAV3H.js";
85
85
  import "./chunk-R3F4K2SB.js";
86
- import "./chunk-MYH6FIBZ.js";
86
+ import "./chunk-SEEWE5SJ.js";
87
87
  import {
88
88
  errMsg
89
89
  } from "./chunk-GDKLH7ZY.js";
@@ -6032,7 +6032,7 @@ Run "xbrowser ${command} ${subCommand} --help" to see available parameters.`
6032
6032
  const targetPage = pages[cmdTabIndex];
6033
6033
  await targetPage.bringToFront().catch(() => {
6034
6034
  });
6035
- const { setActivePage } = await import("./browser-33QQWEDI.js");
6035
+ const { setActivePage } = await import("./browser-NYYCR4O7.js");
6036
6036
  setActivePage(session, targetPage);
6037
6037
  }
6038
6038
  }
@@ -6297,7 +6297,7 @@ async function handleRemote(args, options, mode) {
6297
6297
  }
6298
6298
  }
6299
6299
  async function startMcpServer() {
6300
- const { startMcpStdio } = await import("./server-EZJW6IHU.js");
6300
+ const { startMcpStdio } = await import("./server-65LCP6S6.js");
6301
6301
  startMcpStdio();
6302
6302
  }
6303
6303
 
@@ -6316,7 +6316,7 @@ async function main() {
6316
6316
  const command = process.argv[2];
6317
6317
  const isLongRunning = command === "preview" || command === "serve" || command === "mcp";
6318
6318
  if (!isLongRunning) {
6319
- const { ensureProcessCanExit } = await import("./browser-33QQWEDI.js");
6319
+ const { ensureProcessCanExit } = await import("./browser-NYYCR4O7.js");
6320
6320
  await ensureProcessCanExit().catch(() => {
6321
6321
  });
6322
6322
  process.exit(process.exitCode || exitCode);
@@ -38,10 +38,10 @@ import {
38
38
  resolveLaunchOpts,
39
39
  saveSessionDiskMeta,
40
40
  setActivePage
41
- } from "./chunk-ZOYM7QMS.js";
41
+ } from "./chunk-3PUOISKE.js";
42
42
  import {
43
43
  createRuleEngine
44
- } from "./chunk-CGNHGAIJ.js";
44
+ } from "./chunk-ME5FO5YV.js";
45
45
  import {
46
46
  queryJS
47
47
  } from "./chunk-A7NEA4PA.js";
@@ -50,7 +50,7 @@ import {
50
50
  sleep,
51
51
  wheelDelta
52
52
  } from "./chunk-R3F4K2SB.js";
53
- import "./chunk-MYH6FIBZ.js";
53
+ import "./chunk-SEEWE5SJ.js";
54
54
  import {
55
55
  errMsg
56
56
  } from "./chunk-GDKLH7ZY.js";
@@ -535,10 +535,14 @@ var clickCommand = registerCommand({
535
535
  targetIdsBefore = /* @__PURE__ */ new Set();
536
536
  }
537
537
  const pagePromise = new Promise((resolve) => {
538
+ const waitMs = (() => {
539
+ const v = parseInt(process.env.XBROWSER_NEW_TAB_WAIT ?? "250", 10);
540
+ return Number.isFinite(v) && v > 0 ? v : 250;
541
+ })();
538
542
  const timer = setTimeout(() => {
539
543
  ctx.browserContext.off("page", handler);
540
544
  resolve(void 0);
541
- }, 3e3);
545
+ }, waitMs);
542
546
  const handler = (page2) => {
543
547
  if (targetIdsBefore.has(targetIdOf(page2))) return;
544
548
  try {
@@ -7533,7 +7537,10 @@ function buildSnapshot(elements) {
7533
7537
  }
7534
7538
 
7535
7539
  // src/tips/index.ts
7536
- var DEBOUNCE_MS = 500;
7540
+ function tipsDebounceMs() {
7541
+ const v = parseInt(process.env.XBROWSER_TIPS_DEBOUNCE ?? "120", 10);
7542
+ return Number.isFinite(v) && v > 0 ? v : 0;
7543
+ }
7537
7544
  var SKIP_DETECT_COMMANDS = /* @__PURE__ */ new Set([
7538
7545
  "goto",
7539
7546
  // 导航命令不需要检测
@@ -7625,7 +7632,9 @@ var TipsManager = class {
7625
7632
  }
7626
7633
  }
7627
7634
  debounce() {
7628
- return new Promise((resolve) => setTimeout(resolve, DEBOUNCE_MS));
7635
+ const ms = tipsDebounceMs();
7636
+ if (ms <= 0) return Promise.resolve();
7637
+ return new Promise((resolve) => setTimeout(resolve, ms));
7629
7638
  }
7630
7639
  formatTips(tips) {
7631
7640
  return tips.map((tip) => {
@@ -7871,7 +7880,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
7871
7880
  }
7872
7881
  let targetPageOverride = null;
7873
7882
  if (_target && extraOpts?.cdpEndpoint) {
7874
- const { findTargetPage } = await import("./browser-4LOJSMWV.js");
7883
+ const { findTargetPage } = await import("./browser-ZD53IMUJ.js");
7875
7884
  targetPageOverride = await findTargetPage(extraOpts.cdpEndpoint, _target);
7876
7885
  if (!targetPageOverride) {
7877
7886
  return errorResult(`Target "${_target}" not found. Use 'xbrowser targets --cdp ${extraOpts.cdpEndpoint}' to list available pages.`);
@@ -9711,7 +9720,7 @@ function createRPCHandler() {
9711
9720
  if (isNewFormat) {
9712
9721
  try {
9713
9722
  const replayErrors = [];
9714
- const { SessionReplayer } = await import("./session-replayer-PJIUH7KI.js");
9723
+ const { SessionReplayer } = await import("./session-replayer-TXTNTG33.js");
9715
9724
  const replayer = new SessionReplayer({
9716
9725
  page: session.page,
9717
9726
  stepDelay: slowMo * 500,
package/dist/index.js CHANGED
@@ -48,7 +48,7 @@ import {
48
48
  splitCommand,
49
49
  waitForAIResponse,
50
50
  waitForPage
51
- } from "./chunk-DRKL2RFQ.js";
51
+ } from "./chunk-6OWOICQ3.js";
52
52
  import {
53
53
  SessionRecorder
54
54
  } from "./chunk-IR6C24P7.js";
@@ -63,7 +63,7 @@ import {
63
63
  getBrowser,
64
64
  resetForTesting,
65
65
  saveSessionDiskMeta
66
- } from "./chunk-NNI5RT4A.js";
66
+ } from "./chunk-KECYFWJO.js";
67
67
  import "./chunk-JKVUFP3G.js";
68
68
  import {
69
69
  forwardCommandLog,
@@ -120,9 +120,9 @@ import {
120
120
  generateJSScript,
121
121
  generatePythonScript
122
122
  } from "./chunk-ETCO4SNK.js";
123
- import "./chunk-U6GFDVNI.js";
123
+ import "./chunk-J3HXZVSN.js";
124
124
  import "./chunk-R3F4K2SB.js";
125
- import "./chunk-MYH6FIBZ.js";
125
+ import "./chunk-SEEWE5SJ.js";
126
126
  import {
127
127
  errMsg
128
128
  } from "./chunk-GDKLH7ZY.js";
@@ -6127,7 +6127,7 @@ Run "xbrowser ${command} ${subCommand} --help" to see available parameters.`
6127
6127
  const targetPage = pages[cmdTabIndex];
6128
6128
  await targetPage.bringToFront().catch(() => {
6129
6129
  });
6130
- const { setActivePage } = await import("./browser-L3TUDYY3.js");
6130
+ const { setActivePage } = await import("./browser-4IYIHZ6R.js");
6131
6131
  setActivePage(session, targetPage);
6132
6132
  }
6133
6133
  }
@@ -6392,7 +6392,7 @@ async function handleRemote(args, options, mode) {
6392
6392
  }
6393
6393
  }
6394
6394
  async function startMcpServer() {
6395
- const { startMcpStdio } = await import("./server-IBQGNDSB.js");
6395
+ const { startMcpStdio } = await import("./server-6KZ5NT6L.js");
6396
6396
  startMcpStdio();
6397
6397
  }
6398
6398
 
@@ -9307,7 +9307,7 @@ var DataCollector = class {
9307
9307
  return results;
9308
9308
  }
9309
9309
  async createBrowserContext() {
9310
- const { launch } = await import("./cdp-driver-5YVI5AIS.js");
9310
+ const { launch } = await import("./cdp-driver-EFEW4ZEN.js");
9311
9311
  const { browser } = await launch({
9312
9312
  headless: true,
9313
9313
  args: ["--no-sandbox", "--disable-setuid-sandbox"]
@@ -7,7 +7,7 @@ import {
7
7
  joinCdpHttpUrl,
8
8
  killChrome,
9
9
  launchChrome
10
- } from "./chunk-VCVDILH6.js";
10
+ } from "./chunk-T2AI24A5.js";
11
11
  import "./chunk-3RG5ZIWI.js";
12
12
  export {
13
13
  ANTI_DETECT_ARGS,
@@ -7,7 +7,7 @@ import {
7
7
  joinCdpHttpUrl,
8
8
  killChrome,
9
9
  launchChrome
10
- } from "./chunk-MYH6FIBZ.js";
10
+ } from "./chunk-SEEWE5SJ.js";
11
11
  import "./chunk-GDKLH7ZY.js";
12
12
  import "./chunk-KFQGP6VL.js";
13
13
  export {
@@ -1,17 +1,111 @@
1
1
  import {
2
2
  executeChain,
3
3
  executeCommand
4
- } from "./chunk-PO27V5UH.js";
4
+ } from "./chunk-HDFQRDBC.js";
5
5
  import "./chunk-JKVUFP3G.js";
6
6
  import "./chunk-3OD76SUE.js";
7
7
  import "./chunk-ZTHE5RBZ.js";
8
8
  import "./chunk-Q2DFJQGS.js";
9
- import "./chunk-2EEAXSBA.js";
9
+ import "./chunk-HSYRAV3H.js";
10
10
  import "./chunk-R3F4K2SB.js";
11
- import "./chunk-MYH6FIBZ.js";
11
+ import "./chunk-SEEWE5SJ.js";
12
12
  import "./chunk-GDKLH7ZY.js";
13
13
  import "./chunk-KFQGP6VL.js";
14
14
 
15
+ // src/lib/heal-kb.ts
16
+ import fs from "fs";
17
+ import path from "path";
18
+ import os from "os";
19
+ var HEAL_KB_TTL_DAYS = 30;
20
+ function healKbDir(dir) {
21
+ return dir ?? path.join(os.homedir(), ".xbrowser", "knowledge");
22
+ }
23
+ function domainFile(domain, dir) {
24
+ if (!/^[\w.-]+$/.test(domain)) throw new Error(`invalid domain: ${domain}`);
25
+ return path.join(dir, `heals-${domain}.json`);
26
+ }
27
+ function readHeals(domain, dir) {
28
+ try {
29
+ return JSON.parse(fs.readFileSync(domainFile(domain, healKbDir(dir)), "utf8"));
30
+ } catch {
31
+ return {};
32
+ }
33
+ }
34
+ function lookupHeal(domain, primary, dir) {
35
+ if (!primary) return null;
36
+ const e = readHeals(domain, dir)[primary];
37
+ return e ? { ...e } : null;
38
+ }
39
+ function pruneExpiredHeals(domain, dir) {
40
+ const d = healKbDir(dir);
41
+ const file = domainFile(domain, d);
42
+ const data = readHeals(domain, d);
43
+ const cutoff = Date.now() - HEAL_KB_TTL_DAYS * 864e5;
44
+ let pruned = 0;
45
+ for (const k of Object.keys(data)) {
46
+ const ts = Date.parse(data[k]?.lastSeen ?? "");
47
+ if (!(ts >= cutoff)) {
48
+ delete data[k];
49
+ pruned++;
50
+ }
51
+ }
52
+ if (pruned > 0) {
53
+ fs.mkdirSync(d, { recursive: true });
54
+ fs.writeFileSync(file, JSON.stringify(data, null, 2));
55
+ }
56
+ return pruned;
57
+ }
58
+ function writeHeal(domain, primary, entry, dir) {
59
+ if (!primary) throw new Error("primary selector is required");
60
+ if (!entry.healed || !entry.strategy) throw new Error("healed and strategy are required");
61
+ const d = healKbDir(dir);
62
+ const file = domainFile(domain, d);
63
+ const data = readHeals(domain, d);
64
+ const prev = data[primary];
65
+ const next = {
66
+ healed: entry.healed,
67
+ strategy: entry.strategy,
68
+ lastSeen: (/* @__PURE__ */ new Date()).toISOString(),
69
+ hits: (prev?.hits ?? 0) + 1
70
+ };
71
+ data[primary] = next;
72
+ fs.mkdirSync(d, { recursive: true });
73
+ fs.writeFileSync(file, JSON.stringify(data, null, 2));
74
+ pruneExpiredHeals(domain, d);
75
+ return next;
76
+ }
77
+ function forgetHeal(domain, primary, dir) {
78
+ const d = healKbDir(dir);
79
+ const data = readHeals(domain, d);
80
+ if (!data[primary]) return false;
81
+ delete data[primary];
82
+ fs.writeFileSync(domainFile(domain, d), JSON.stringify(data, null, 2));
83
+ return true;
84
+ }
85
+ function listHealDomains(dir) {
86
+ const d = healKbDir(dir);
87
+ let files = [];
88
+ try {
89
+ files = fs.readdirSync(d).filter((f) => f.startsWith("heals-") && f.endsWith(".json"));
90
+ } catch {
91
+ return [];
92
+ }
93
+ const out = [];
94
+ for (const f of files) {
95
+ const domain = f.slice("heals-".length, -".json".length);
96
+ const data = readHeals(domain, d);
97
+ const entries = Object.values(data);
98
+ if (entries.length === 0) continue;
99
+ out.push({
100
+ domain,
101
+ entries: entries.length,
102
+ hits: entries.reduce((s, e) => s + (e.hits ?? 0), 0),
103
+ lastSeen: entries.reduce((m, e) => e.lastSeen > m ? e.lastSeen : m, "")
104
+ });
105
+ }
106
+ return out.sort((a, b) => b.lastSeen.localeCompare(a.lastSeen));
107
+ }
108
+
15
109
  // src/mcp/server.ts
16
110
  var TOOLS = [
17
111
  {
@@ -109,6 +203,32 @@ var TOOLS = [
109
203
  },
110
204
  required: ["file"]
111
205
  }
206
+ },
207
+ {
208
+ name: "heal_kb_read",
209
+ description: "Read the self-healing knowledge base (~/.xbrowser/knowledge/heals-{domain}.json). Mappings: broken primary selector \u2192 healed selector + strategy + hits. No args = list all domains; domain only = all entries for that domain; domain+selector = single lookup.",
210
+ inputSchema: {
211
+ type: "object",
212
+ properties: {
213
+ domain: { type: "string", description: "Site domain, e.g. juejin.cn. Omit to list all domains." },
214
+ selector: { type: "string", description: "Broken primary selector to look up (requires domain)." }
215
+ }
216
+ }
217
+ },
218
+ {
219
+ name: "heal_kb_write",
220
+ description: "Write or remove an entry in the self-healing knowledge base \u2014 lets an external healer agent (selector-healer) persist re-located selectors that the replay engine will pick up as known-heal. Entries TTL-prune after 30 days without a hit.",
221
+ inputSchema: {
222
+ type: "object",
223
+ properties: {
224
+ domain: { type: "string", description: "Site domain, e.g. juejin.cn" },
225
+ selector: { type: "string", description: "Broken primary selector (the recording-time selector)" },
226
+ healed: { type: "string", description: "The re-located working selector" },
227
+ strategy: { type: "string", description: "How it was healed, e.g. partial-id / text-anchor / label-anchor" },
228
+ remove: { type: "boolean", description: "true = forget this entry instead of writing" }
229
+ },
230
+ required: ["domain", "selector"]
231
+ }
112
232
  }
113
233
  ];
114
234
  var DEFAULT_SESSION = "mcp";
@@ -162,6 +282,28 @@ async function runTool(name, args) {
162
282
  const result = await executeCommand("replay", params, session, {});
163
283
  return text(result);
164
284
  }
285
+ case "heal_kb_read": {
286
+ const domain = args.domain;
287
+ const selector = args.selector;
288
+ if (!domain) return text({ domains: listHealDomains() });
289
+ if (selector) {
290
+ const entry = lookupHeal(domain, selector);
291
+ return text({ domain, selector, entry });
292
+ }
293
+ return text({ domain, entries: readHeals(domain) });
294
+ }
295
+ case "heal_kb_write": {
296
+ const domain = args.domain;
297
+ const selector = args.selector;
298
+ if (args.remove) {
299
+ return text({ domain, selector, removed: forgetHeal(domain, selector) });
300
+ }
301
+ const entry = writeHeal(domain, selector, {
302
+ healed: args.healed,
303
+ strategy: args.strategy
304
+ });
305
+ return text({ domain, selector, written: entry });
306
+ }
165
307
  default:
166
308
  throw new Error(`unknown tool: ${name}`);
167
309
  }
@@ -236,5 +378,6 @@ function startMcpStdio() {
236
378
  });
237
379
  }
238
380
  export {
381
+ runTool,
239
382
  startMcpStdio
240
383
  };
@@ -1,20 +1,114 @@
1
1
  import {
2
2
  executeChain,
3
3
  executeCommand
4
- } from "./chunk-DRKL2RFQ.js";
5
- import "./chunk-NNI5RT4A.js";
4
+ } from "./chunk-6OWOICQ3.js";
5
+ import "./chunk-KECYFWJO.js";
6
6
  import "./chunk-JKVUFP3G.js";
7
7
  import "./chunk-3OD76SUE.js";
8
8
  import "./chunk-ZTHE5RBZ.js";
9
9
  import "./chunk-Q2DFJQGS.js";
10
- import "./chunk-U6GFDVNI.js";
10
+ import "./chunk-J3HXZVSN.js";
11
11
  import "./chunk-R3F4K2SB.js";
12
- import "./chunk-MYH6FIBZ.js";
12
+ import "./chunk-SEEWE5SJ.js";
13
13
  import "./chunk-GDKLH7ZY.js";
14
14
  import "./chunk-WYETQ5C7.js";
15
15
  import "./chunk-75DNUI6M.js";
16
16
  import "./chunk-KFQGP6VL.js";
17
17
 
18
+ // src/lib/heal-kb.ts
19
+ import fs from "fs";
20
+ import path from "path";
21
+ import os from "os";
22
+ var HEAL_KB_TTL_DAYS = 30;
23
+ function healKbDir(dir) {
24
+ return dir ?? path.join(os.homedir(), ".xbrowser", "knowledge");
25
+ }
26
+ function domainFile(domain, dir) {
27
+ if (!/^[\w.-]+$/.test(domain)) throw new Error(`invalid domain: ${domain}`);
28
+ return path.join(dir, `heals-${domain}.json`);
29
+ }
30
+ function readHeals(domain, dir) {
31
+ try {
32
+ return JSON.parse(fs.readFileSync(domainFile(domain, healKbDir(dir)), "utf8"));
33
+ } catch {
34
+ return {};
35
+ }
36
+ }
37
+ function lookupHeal(domain, primary, dir) {
38
+ if (!primary) return null;
39
+ const e = readHeals(domain, dir)[primary];
40
+ return e ? { ...e } : null;
41
+ }
42
+ function pruneExpiredHeals(domain, dir) {
43
+ const d = healKbDir(dir);
44
+ const file = domainFile(domain, d);
45
+ const data = readHeals(domain, d);
46
+ const cutoff = Date.now() - HEAL_KB_TTL_DAYS * 864e5;
47
+ let pruned = 0;
48
+ for (const k of Object.keys(data)) {
49
+ const ts = Date.parse(data[k]?.lastSeen ?? "");
50
+ if (!(ts >= cutoff)) {
51
+ delete data[k];
52
+ pruned++;
53
+ }
54
+ }
55
+ if (pruned > 0) {
56
+ fs.mkdirSync(d, { recursive: true });
57
+ fs.writeFileSync(file, JSON.stringify(data, null, 2));
58
+ }
59
+ return pruned;
60
+ }
61
+ function writeHeal(domain, primary, entry, dir) {
62
+ if (!primary) throw new Error("primary selector is required");
63
+ if (!entry.healed || !entry.strategy) throw new Error("healed and strategy are required");
64
+ const d = healKbDir(dir);
65
+ const file = domainFile(domain, d);
66
+ const data = readHeals(domain, d);
67
+ const prev = data[primary];
68
+ const next = {
69
+ healed: entry.healed,
70
+ strategy: entry.strategy,
71
+ lastSeen: (/* @__PURE__ */ new Date()).toISOString(),
72
+ hits: (prev?.hits ?? 0) + 1
73
+ };
74
+ data[primary] = next;
75
+ fs.mkdirSync(d, { recursive: true });
76
+ fs.writeFileSync(file, JSON.stringify(data, null, 2));
77
+ pruneExpiredHeals(domain, d);
78
+ return next;
79
+ }
80
+ function forgetHeal(domain, primary, dir) {
81
+ const d = healKbDir(dir);
82
+ const data = readHeals(domain, d);
83
+ if (!data[primary]) return false;
84
+ delete data[primary];
85
+ fs.writeFileSync(domainFile(domain, d), JSON.stringify(data, null, 2));
86
+ return true;
87
+ }
88
+ function listHealDomains(dir) {
89
+ const d = healKbDir(dir);
90
+ let files = [];
91
+ try {
92
+ files = fs.readdirSync(d).filter((f) => f.startsWith("heals-") && f.endsWith(".json"));
93
+ } catch {
94
+ return [];
95
+ }
96
+ const out = [];
97
+ for (const f of files) {
98
+ const domain = f.slice("heals-".length, -".json".length);
99
+ const data = readHeals(domain, d);
100
+ const entries = Object.values(data);
101
+ if (entries.length === 0) continue;
102
+ out.push({
103
+ domain,
104
+ entries: entries.length,
105
+ hits: entries.reduce((s, e) => s + (e.hits ?? 0), 0),
106
+ lastSeen: entries.reduce((m, e) => e.lastSeen > m ? e.lastSeen : m, "")
107
+ });
108
+ }
109
+ return out.sort((a, b) => b.lastSeen.localeCompare(a.lastSeen));
110
+ }
111
+
18
112
  // src/mcp/server.ts
19
113
  var TOOLS = [
20
114
  {
@@ -112,6 +206,32 @@ var TOOLS = [
112
206
  },
113
207
  required: ["file"]
114
208
  }
209
+ },
210
+ {
211
+ name: "heal_kb_read",
212
+ description: "Read the self-healing knowledge base (~/.xbrowser/knowledge/heals-{domain}.json). Mappings: broken primary selector \u2192 healed selector + strategy + hits. No args = list all domains; domain only = all entries for that domain; domain+selector = single lookup.",
213
+ inputSchema: {
214
+ type: "object",
215
+ properties: {
216
+ domain: { type: "string", description: "Site domain, e.g. juejin.cn. Omit to list all domains." },
217
+ selector: { type: "string", description: "Broken primary selector to look up (requires domain)." }
218
+ }
219
+ }
220
+ },
221
+ {
222
+ name: "heal_kb_write",
223
+ description: "Write or remove an entry in the self-healing knowledge base \u2014 lets an external healer agent (selector-healer) persist re-located selectors that the replay engine will pick up as known-heal. Entries TTL-prune after 30 days without a hit.",
224
+ inputSchema: {
225
+ type: "object",
226
+ properties: {
227
+ domain: { type: "string", description: "Site domain, e.g. juejin.cn" },
228
+ selector: { type: "string", description: "Broken primary selector (the recording-time selector)" },
229
+ healed: { type: "string", description: "The re-located working selector" },
230
+ strategy: { type: "string", description: "How it was healed, e.g. partial-id / text-anchor / label-anchor" },
231
+ remove: { type: "boolean", description: "true = forget this entry instead of writing" }
232
+ },
233
+ required: ["domain", "selector"]
234
+ }
115
235
  }
116
236
  ];
117
237
  var DEFAULT_SESSION = "mcp";
@@ -165,6 +285,28 @@ async function runTool(name, args) {
165
285
  const result = await executeCommand("replay", params, session, {});
166
286
  return text(result);
167
287
  }
288
+ case "heal_kb_read": {
289
+ const domain = args.domain;
290
+ const selector = args.selector;
291
+ if (!domain) return text({ domains: listHealDomains() });
292
+ if (selector) {
293
+ const entry = lookupHeal(domain, selector);
294
+ return text({ domain, selector, entry });
295
+ }
296
+ return text({ domain, entries: readHeals(domain) });
297
+ }
298
+ case "heal_kb_write": {
299
+ const domain = args.domain;
300
+ const selector = args.selector;
301
+ if (args.remove) {
302
+ return text({ domain, selector, removed: forgetHeal(domain, selector) });
303
+ }
304
+ const entry = writeHeal(domain, selector, {
305
+ healed: args.healed,
306
+ strategy: args.strategy
307
+ });
308
+ return text({ domain, selector, written: entry });
309
+ }
168
310
  default:
169
311
  throw new Error(`unknown tool: ${name}`);
170
312
  }
@@ -239,5 +381,6 @@ function startMcpStdio() {
239
381
  });
240
382
  }
241
383
  export {
384
+ runTool,
242
385
  startMcpStdio
243
386
  };
@@ -5,10 +5,91 @@ import {
5
5
  import "./chunk-KFQGP6VL.js";
6
6
 
7
7
  // src/recorder/session-replayer.ts
8
- import { readFileSync, writeFileSync, mkdirSync } from "fs";
9
8
  import { join } from "path";
10
9
  import { homedir } from "os";
10
+
11
+ // src/lib/heal-kb.ts
12
+ import fs from "fs";
13
+ import path from "path";
14
+ import os from "os";
11
15
  var HEAL_KB_TTL_DAYS = 30;
16
+ function healKbDir(dir) {
17
+ return dir ?? path.join(os.homedir(), ".xbrowser", "knowledge");
18
+ }
19
+ function domainFile(domain, dir) {
20
+ if (!/^[\w.-]+$/.test(domain)) throw new Error(`invalid domain: ${domain}`);
21
+ return path.join(dir, `heals-${domain}.json`);
22
+ }
23
+ function readHeals(domain, dir) {
24
+ try {
25
+ return JSON.parse(fs.readFileSync(domainFile(domain, healKbDir(dir)), "utf8"));
26
+ } catch {
27
+ return {};
28
+ }
29
+ }
30
+ function lookupHeal(domain, primary, dir) {
31
+ if (!primary) return null;
32
+ const e = readHeals(domain, dir)[primary];
33
+ return e ? { ...e } : null;
34
+ }
35
+ function pruneExpiredHeals(domain, dir) {
36
+ const d = healKbDir(dir);
37
+ const file = domainFile(domain, d);
38
+ const data = readHeals(domain, d);
39
+ const cutoff = Date.now() - HEAL_KB_TTL_DAYS * 864e5;
40
+ let pruned = 0;
41
+ for (const k of Object.keys(data)) {
42
+ const ts = Date.parse(data[k]?.lastSeen ?? "");
43
+ if (!(ts >= cutoff)) {
44
+ delete data[k];
45
+ pruned++;
46
+ }
47
+ }
48
+ if (pruned > 0) {
49
+ fs.mkdirSync(d, { recursive: true });
50
+ fs.writeFileSync(file, JSON.stringify(data, null, 2));
51
+ }
52
+ return pruned;
53
+ }
54
+ function writeHeal(domain, primary, entry, dir) {
55
+ if (!primary) throw new Error("primary selector is required");
56
+ if (!entry.healed || !entry.strategy) throw new Error("healed and strategy are required");
57
+ const d = healKbDir(dir);
58
+ const file = domainFile(domain, d);
59
+ const data = readHeals(domain, d);
60
+ const prev = data[primary];
61
+ const next = {
62
+ healed: entry.healed,
63
+ strategy: entry.strategy,
64
+ lastSeen: (/* @__PURE__ */ new Date()).toISOString(),
65
+ hits: (prev?.hits ?? 0) + 1
66
+ };
67
+ data[primary] = next;
68
+ fs.mkdirSync(d, { recursive: true });
69
+ fs.writeFileSync(file, JSON.stringify(data, null, 2));
70
+ pruneExpiredHeals(domain, d);
71
+ return next;
72
+ }
73
+ function bumpHeal(domain, primary, dir) {
74
+ const d = healKbDir(dir);
75
+ const data = readHeals(domain, d);
76
+ const e = data[primary];
77
+ if (!e) return false;
78
+ e.hits += 1;
79
+ e.lastSeen = (/* @__PURE__ */ new Date()).toISOString();
80
+ fs.writeFileSync(domainFile(domain, d), JSON.stringify(data, null, 2));
81
+ return true;
82
+ }
83
+ function forgetHeal(domain, primary, dir) {
84
+ const d = healKbDir(dir);
85
+ const data = readHeals(domain, d);
86
+ if (!data[primary]) return false;
87
+ delete data[primary];
88
+ fs.writeFileSync(domainFile(domain, d), JSON.stringify(data, null, 2));
89
+ return true;
90
+ }
91
+
92
+ // src/recorder/session-replayer.ts
12
93
  var NATIVE_VALUE_INJECT_TYPES = /* @__PURE__ */ new Set([
13
94
  "color",
14
95
  "date",
@@ -38,8 +119,8 @@ var SessionReplayer = class {
38
119
  /** Load a recording from a file path or parsed JSON */
39
120
  async load(source) {
40
121
  if (typeof source === "string") {
41
- const fs = await import("fs");
42
- const raw = fs.readFileSync(source, "utf8");
122
+ const fs2 = await import("fs");
123
+ const raw = fs2.readFileSync(source, "utf8");
43
124
  this.recording = JSON.parse(raw);
44
125
  } else {
45
126
  this.recording = source;
@@ -61,7 +142,7 @@ var SessionReplayer = class {
61
142
  if (this.opts.page) {
62
143
  this.page = this.opts.page;
63
144
  } else if (this.opts.cdpUrl) {
64
- const { launch } = await import("./cdp-driver-BYW7WY7E.js");
145
+ const { launch } = await import("./cdp-driver-4X6Q62WI.js");
65
146
  const { browser } = await launch({ cdpEndpoint: this.opts.cdpUrl });
66
147
  let contexts = browser.contexts();
67
148
  for (let i = 0; i < 10 && contexts.length === 0; i++) {
@@ -719,62 +800,26 @@ var SessionReplayer = class {
719
800
  return "unknown";
720
801
  }
721
802
  }
722
- healKnowledgeFile(domain) {
723
- return join(this.opts.healKnowledgeDir, `heals-${domain}.json`);
724
- }
725
- readHealFile(file) {
726
- try {
727
- return JSON.parse(readFileSync(file, "utf8"));
728
- } catch {
729
- return {};
730
- }
731
- }
732
803
  lookupHealKnowledge(domain, primary) {
733
804
  if (!primary || !this.opts.healKnowledgeDir) return null;
734
- const data = this.readHealFile(this.healKnowledgeFile(domain));
735
- const e = data[primary];
736
- return e ? { healed: e.healed, strategy: e.strategy } : null;
805
+ return lookupHeal(domain, primary, this.opts.healKnowledgeDir);
737
806
  }
738
807
  persistHealKnowledge(action, primary, healed) {
739
808
  if (!this.opts.healKnowledgeDir || !primary || healed.strategy === "known-heal") return;
740
809
  try {
741
- const file = this.healKnowledgeFile(this.pageDomain(action));
742
- const data = this.readHealFile(file);
743
- data[primary] = {
744
- healed: healed.selector,
745
- strategy: healed.strategy,
746
- lastSeen: (/* @__PURE__ */ new Date()).toISOString(),
747
- hits: (data[primary]?.hits ?? 0) + 1
748
- };
749
- const cutoff = Date.now() - HEAL_KB_TTL_DAYS * 864e5;
750
- for (const k of Object.keys(data)) {
751
- const ts = Date.parse(data[k]?.lastSeen ?? "");
752
- if (!(ts >= cutoff)) delete data[k];
753
- }
754
- mkdirSync(this.opts.healKnowledgeDir, { recursive: true });
755
- writeFileSync(file, JSON.stringify(data, null, 2));
810
+ writeHeal(this.pageDomain(action), primary, { healed: healed.selector, strategy: healed.strategy }, this.opts.healKnowledgeDir);
756
811
  } catch {
757
812
  }
758
813
  }
759
814
  bumpHealKnowledge(domain, primary) {
760
815
  try {
761
- const file = this.healKnowledgeFile(domain);
762
- const data = this.readHealFile(file);
763
- const e = data[primary];
764
- if (!e) return;
765
- e.hits += 1;
766
- e.lastSeen = (/* @__PURE__ */ new Date()).toISOString();
767
- writeFileSync(file, JSON.stringify(data, null, 2));
816
+ bumpHeal(domain, primary, this.opts.healKnowledgeDir);
768
817
  } catch {
769
818
  }
770
819
  }
771
820
  forgetHealKnowledge(domain, primary) {
772
821
  try {
773
- const file = this.healKnowledgeFile(domain);
774
- const data = this.readHealFile(file);
775
- if (!data[primary]) return;
776
- delete data[primary];
777
- writeFileSync(file, JSON.stringify(data, null, 2));
822
+ forgetHeal(domain, primary, this.opts.healKnowledgeDir);
778
823
  } catch {
779
824
  }
780
825
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xbrowser/cli",
3
- "version": "1.25.2",
3
+ "version": "1.26.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": {