@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.
- package/dist/{browser-L3TUDYY3.js → browser-4IYIHZ6R.js} +3 -3
- package/dist/{browser-33QQWEDI.js → browser-NYYCR4O7.js} +2 -2
- package/dist/{browser-4LOJSMWV.js → browser-ZD53IMUJ.js} +3 -3
- package/dist/{cdp-driver-BYW7WY7E.js → cdp-driver-4X6Q62WI.js} +2 -2
- package/dist/{cdp-driver-5YVI5AIS.js → cdp-driver-EFEW4ZEN.js} +2 -2
- package/dist/{cdp-driver-MEK5EDEK.js → cdp-driver-HB744QB4.js} +3 -3
- package/dist/{chunk-ZOYM7QMS.js → chunk-3PUOISKE.js} +1 -1
- package/dist/{chunk-DRKL2RFQ.js → chunk-6OWOICQ3.js} +14 -5
- package/dist/{chunk-PO27V5UH.js → chunk-HDFQRDBC.js} +14 -5
- package/dist/{chunk-2EEAXSBA.js → chunk-HSYRAV3H.js} +3 -3
- package/dist/{chunk-U6GFDVNI.js → chunk-J3HXZVSN.js} +3 -3
- package/dist/{chunk-NNI5RT4A.js → chunk-KECYFWJO.js} +1 -1
- package/dist/{chunk-CGNHGAIJ.js → chunk-ME5FO5YV.js} +3 -3
- package/dist/{chunk-MYH6FIBZ.js → chunk-SEEWE5SJ.js} +5 -1
- package/dist/{chunk-VCVDILH6.js → chunk-T2AI24A5.js} +5 -1
- package/dist/cli.js +6 -6
- package/dist/daemon-main.js +17 -8
- package/dist/index.js +7 -7
- package/dist/{launcher-LUOMMGZC.js → launcher-SJ44X25U.js} +1 -1
- package/dist/{launcher-IK2FKW6T.js → launcher-XHKIC4GP.js} +1 -1
- package/dist/{server-EZJW6IHU.js → server-65LCP6S6.js} +146 -3
- package/dist/{server-IBQGNDSB.js → server-6KZ5NT6L.js} +147 -4
- package/dist/{session-replayer-PJIUH7KI.js → session-replayer-TXTNTG33.js} +89 -44
- package/package.json +1 -1
|
@@ -20,10 +20,10 @@ import {
|
|
|
20
20
|
saveSessionDiskMeta,
|
|
21
21
|
setActivePage,
|
|
22
22
|
touchSession
|
|
23
|
-
} from "./chunk-
|
|
24
|
-
import "./chunk-
|
|
23
|
+
} from "./chunk-KECYFWJO.js";
|
|
24
|
+
import "./chunk-J3HXZVSN.js";
|
|
25
25
|
import "./chunk-R3F4K2SB.js";
|
|
26
|
-
import "./chunk-
|
|
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-
|
|
23
|
+
} from "./chunk-HSYRAV3H.js";
|
|
24
24
|
import "./chunk-R3F4K2SB.js";
|
|
25
|
-
import "./chunk-
|
|
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-
|
|
24
|
-
import "./chunk-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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) {
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
resolveLaunchOpts,
|
|
10
10
|
saveSessionDiskMeta,
|
|
11
11
|
setActivePage
|
|
12
|
-
} from "./chunk-
|
|
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
|
-
},
|
|
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
|
-
|
|
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
|
-
|
|
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-
|
|
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-
|
|
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
|
-
},
|
|
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
|
-
|
|
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
|
-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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) {
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
import {
|
|
14
14
|
connectToCDP,
|
|
15
15
|
launchChrome
|
|
16
|
-
} from "./chunk-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
84
|
+
} from "./chunk-HSYRAV3H.js";
|
|
85
85
|
import "./chunk-R3F4K2SB.js";
|
|
86
|
-
import "./chunk-
|
|
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-
|
|
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-
|
|
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-
|
|
6319
|
+
const { ensureProcessCanExit } = await import("./browser-NYYCR4O7.js");
|
|
6320
6320
|
await ensureProcessCanExit().catch(() => {
|
|
6321
6321
|
});
|
|
6322
6322
|
process.exit(process.exitCode || exitCode);
|
package/dist/daemon-main.js
CHANGED
|
@@ -38,10 +38,10 @@ import {
|
|
|
38
38
|
resolveLaunchOpts,
|
|
39
39
|
saveSessionDiskMeta,
|
|
40
40
|
setActivePage
|
|
41
|
-
} from "./chunk-
|
|
41
|
+
} from "./chunk-3PUOISKE.js";
|
|
42
42
|
import {
|
|
43
43
|
createRuleEngine
|
|
44
|
-
} from "./chunk-
|
|
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-
|
|
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
|
-
},
|
|
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
|
-
|
|
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
|
-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
123
|
+
import "./chunk-J3HXZVSN.js";
|
|
124
124
|
import "./chunk-R3F4K2SB.js";
|
|
125
|
-
import "./chunk-
|
|
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-
|
|
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-
|
|
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-
|
|
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"]
|
|
@@ -1,17 +1,111 @@
|
|
|
1
1
|
import {
|
|
2
2
|
executeChain,
|
|
3
3
|
executeCommand
|
|
4
|
-
} from "./chunk-
|
|
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-
|
|
9
|
+
import "./chunk-HSYRAV3H.js";
|
|
10
10
|
import "./chunk-R3F4K2SB.js";
|
|
11
|
-
import "./chunk-
|
|
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-
|
|
5
|
-
import "./chunk-
|
|
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-
|
|
10
|
+
import "./chunk-J3HXZVSN.js";
|
|
11
11
|
import "./chunk-R3F4K2SB.js";
|
|
12
|
-
import "./chunk-
|
|
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
|
|
42
|
-
const raw =
|
|
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-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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": {
|