@xbrowser/cli 1.23.1 → 1.24.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/{chunk-TBSS7ES4.js → chunk-A7RZYSWM.js} +49 -3
- package/dist/{chunk-LFX24QJZ.js → chunk-BROEUPL3.js} +50 -4
- package/dist/{chunk-LPW6QS4M.js → chunk-FNXA6FDM.js} +39 -1
- package/dist/{chunk-FBQZ3PVD.js → chunk-PGKV24QN.js} +39 -1
- package/dist/{chunk-W44UXZSC.js → chunk-SOPN4XDK.js} +39 -1
- package/dist/cli.js +10 -2
- package/dist/daemon-main.js +50 -4
- package/dist/{human-interaction-MXST5ZQM.js → human-interaction-CTZ4IWXJ.js} +1 -1
- package/dist/{human-interaction-67K43B4O.js → human-interaction-HJCK45SL.js} +1 -1
- package/dist/{human-interaction-DK6QGYNC.js → human-interaction-V3VDGHHI.js} +1 -1
- package/dist/index.d.ts +16 -0
- package/dist/index.js +11 -3
- package/dist/{recovery-LGY25LIM.js → recovery-5C4FBA5O.js} +1 -1
- package/dist/{recovery-67DMH2OM.js → recovery-EBKORYEU.js} +3 -3
- package/dist/{recovery-6RI2FE3E.js → recovery-IMZUGWSS.js} +3 -3
- package/dist/{server-DZUIBVVR.js → server-NPINOVJS.js} +1 -1
- package/dist/{server-HU4F5KN7.js → server-YVMWMNHD.js} +1 -1
- package/package.json +2 -2
|
@@ -30,6 +30,35 @@ import {
|
|
|
30
30
|
errMsg
|
|
31
31
|
} from "./chunk-GDKLH7ZY.js";
|
|
32
32
|
|
|
33
|
+
// src/fail-codes.ts
|
|
34
|
+
var FAIL_CODES = {
|
|
35
|
+
selectorNotFound: "RETRYABLE: selector-not-found",
|
|
36
|
+
pageNotReady: "RETRYABLE: page-not-ready",
|
|
37
|
+
elementGone: "RETRYABLE: element-gone",
|
|
38
|
+
captcha: "HUMAN: captcha",
|
|
39
|
+
loginWall: "HUMAN: login-wall",
|
|
40
|
+
permissionDenied: "FATAL: permission-denied",
|
|
41
|
+
targetGone: "FATAL: target-gone",
|
|
42
|
+
unknown: "FATAL: unknown"
|
|
43
|
+
};
|
|
44
|
+
function classifyFailure(message) {
|
|
45
|
+
const msg = (message || "").toLowerCase();
|
|
46
|
+
const pick = (code) => ({
|
|
47
|
+
code,
|
|
48
|
+
level: code.split(":")[0],
|
|
49
|
+
message: message || code
|
|
50
|
+
});
|
|
51
|
+
if (/captcha|验证码|人机校验|recaptcha|hcaptcha|turnstile/.test(msg)) return pick(FAIL_CODES.captcha);
|
|
52
|
+
if (/login|登录|signin|sign in|未登录|login-required|LOGIN_REQUIRED/.test(msg)) return pick(FAIL_CODES.loginWall);
|
|
53
|
+
if (/permission|权限|not allowed|forbidden|EACCES/.test(msg)) return pick(FAIL_CODES.permissionDenied);
|
|
54
|
+
if (/selector|选择器|element not found|未找到元素|no such element|queryselector returned null/.test(msg))
|
|
55
|
+
return pick(FAIL_CODES.selectorNotFound);
|
|
56
|
+
if (/timeout|超时|timed out|waiting for/.test(msg)) return pick(FAIL_CODES.pageNotReady);
|
|
57
|
+
if (/net::err|404|navigation failed|页面不存在|target closed|element-gone/.test(msg))
|
|
58
|
+
return pick(FAIL_CODES.targetGone);
|
|
59
|
+
return pick(FAIL_CODES.unknown);
|
|
60
|
+
}
|
|
61
|
+
|
|
33
62
|
// src/executor.ts
|
|
34
63
|
import {
|
|
35
64
|
ok as ok30,
|
|
@@ -8385,7 +8414,16 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
8385
8414
|
if (isSuccess) {
|
|
8386
8415
|
return { ...ok30(raw.data, merged.length > 0 ? merged : raw.tips), duration, ...hookOutputs ? { hookOutputs } : {} };
|
|
8387
8416
|
}
|
|
8388
|
-
|
|
8417
|
+
const fc = classifyFailure(raw.message);
|
|
8418
|
+
return {
|
|
8419
|
+
success: false,
|
|
8420
|
+
data: raw.data,
|
|
8421
|
+
message: raw.message,
|
|
8422
|
+
tips: mergedOrRaw,
|
|
8423
|
+
duration,
|
|
8424
|
+
error: { code: fc.code, level: fc.level },
|
|
8425
|
+
...hookOutputs ? { hookOutputs } : {}
|
|
8426
|
+
};
|
|
8389
8427
|
}
|
|
8390
8428
|
const smartTipNormalized = normalizeTips6(smartTips);
|
|
8391
8429
|
recordArchive(session?.id, sessionName, {
|
|
@@ -8404,7 +8442,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
8404
8442
|
const errorMessage = errMsg(err);
|
|
8405
8443
|
if (session?.page && process.env.XBROWSER_RECOVERY && !extraOpts?._recoveryAttempted) {
|
|
8406
8444
|
try {
|
|
8407
|
-
const { attemptRecovery } = await import("./recovery-
|
|
8445
|
+
const { attemptRecovery } = await import("./recovery-EBKORYEU.js");
|
|
8408
8446
|
const recovery = await attemptRecovery(
|
|
8409
8447
|
session.page,
|
|
8410
8448
|
sessionName,
|
|
@@ -8520,7 +8558,15 @@ async function executeChain(input, options) {
|
|
|
8520
8558
|
continue;
|
|
8521
8559
|
}
|
|
8522
8560
|
const pluginArgs = cmdArgs.slice(1);
|
|
8523
|
-
|
|
8561
|
+
let pluginParams = parsePluginParams(pluginArgs, cmdEntry.parameters);
|
|
8562
|
+
if (cmdEntry.parameters) {
|
|
8563
|
+
const schemaCheck = cmdEntry.parameters.safeParse(pluginParams);
|
|
8564
|
+
if (schemaCheck.success) {
|
|
8565
|
+
for (const [k, v] of Object.entries(schemaCheck.data)) {
|
|
8566
|
+
if (!(k in pluginParams)) pluginParams[k] = v;
|
|
8567
|
+
}
|
|
8568
|
+
}
|
|
8569
|
+
}
|
|
8524
8570
|
const pluginCtx = {
|
|
8525
8571
|
args: pluginArgs,
|
|
8526
8572
|
options: pluginParams,
|
|
@@ -7123,7 +7123,7 @@ function attachWaitForHuman(ctx, getOrCreateWSServer) {
|
|
|
7123
7123
|
if (!ctx.page) {
|
|
7124
7124
|
throw new Error("waitForHuman requires an active page");
|
|
7125
7125
|
}
|
|
7126
|
-
const { HumanInteractionManager } = await import("./human-interaction-
|
|
7126
|
+
const { HumanInteractionManager } = await import("./human-interaction-HJCK45SL.js");
|
|
7127
7127
|
const wsServer2 = await getOrCreateWSServer(ctx.browserContext);
|
|
7128
7128
|
const manager = new HumanInteractionManager(wsServer2, ctx.page);
|
|
7129
7129
|
return manager.waitForHuman(options);
|
|
@@ -7558,6 +7558,35 @@ var XBrowserPluginLoader = class {
|
|
|
7558
7558
|
}
|
|
7559
7559
|
};
|
|
7560
7560
|
|
|
7561
|
+
// src/fail-codes.ts
|
|
7562
|
+
var FAIL_CODES = {
|
|
7563
|
+
selectorNotFound: "RETRYABLE: selector-not-found",
|
|
7564
|
+
pageNotReady: "RETRYABLE: page-not-ready",
|
|
7565
|
+
elementGone: "RETRYABLE: element-gone",
|
|
7566
|
+
captcha: "HUMAN: captcha",
|
|
7567
|
+
loginWall: "HUMAN: login-wall",
|
|
7568
|
+
permissionDenied: "FATAL: permission-denied",
|
|
7569
|
+
targetGone: "FATAL: target-gone",
|
|
7570
|
+
unknown: "FATAL: unknown"
|
|
7571
|
+
};
|
|
7572
|
+
function classifyFailure(message) {
|
|
7573
|
+
const msg = (message || "").toLowerCase();
|
|
7574
|
+
const pick = (code) => ({
|
|
7575
|
+
code,
|
|
7576
|
+
level: code.split(":")[0],
|
|
7577
|
+
message: message || code
|
|
7578
|
+
});
|
|
7579
|
+
if (/captcha|验证码|人机校验|recaptcha|hcaptcha|turnstile/.test(msg)) return pick(FAIL_CODES.captcha);
|
|
7580
|
+
if (/login|登录|signin|sign in|未登录|login-required|LOGIN_REQUIRED/.test(msg)) return pick(FAIL_CODES.loginWall);
|
|
7581
|
+
if (/permission|权限|not allowed|forbidden|EACCES/.test(msg)) return pick(FAIL_CODES.permissionDenied);
|
|
7582
|
+
if (/selector|选择器|element not found|未找到元素|no such element|queryselector returned null/.test(msg))
|
|
7583
|
+
return pick(FAIL_CODES.selectorNotFound);
|
|
7584
|
+
if (/timeout|超时|timed out|waiting for/.test(msg)) return pick(FAIL_CODES.pageNotReady);
|
|
7585
|
+
if (/net::err|404|navigation failed|页面不存在|target closed|element-gone/.test(msg))
|
|
7586
|
+
return pick(FAIL_CODES.targetGone);
|
|
7587
|
+
return pick(FAIL_CODES.unknown);
|
|
7588
|
+
}
|
|
7589
|
+
|
|
7561
7590
|
// src/executor.ts
|
|
7562
7591
|
import {
|
|
7563
7592
|
ok as ok30,
|
|
@@ -8705,7 +8734,16 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
8705
8734
|
if (isSuccess) {
|
|
8706
8735
|
return { ...ok30(raw.data, merged.length > 0 ? merged : raw.tips), duration, ...hookOutputs ? { hookOutputs } : {} };
|
|
8707
8736
|
}
|
|
8708
|
-
|
|
8737
|
+
const fc = classifyFailure(raw.message);
|
|
8738
|
+
return {
|
|
8739
|
+
success: false,
|
|
8740
|
+
data: raw.data,
|
|
8741
|
+
message: raw.message,
|
|
8742
|
+
tips: mergedOrRaw,
|
|
8743
|
+
duration,
|
|
8744
|
+
error: { code: fc.code, level: fc.level },
|
|
8745
|
+
...hookOutputs ? { hookOutputs } : {}
|
|
8746
|
+
};
|
|
8709
8747
|
}
|
|
8710
8748
|
const smartTipNormalized = normalizeTips6(smartTips);
|
|
8711
8749
|
recordArchive(session?.id, sessionName, {
|
|
@@ -8724,7 +8762,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
8724
8762
|
const errorMessage = errMsg(err);
|
|
8725
8763
|
if (session?.page && process.env.XBROWSER_RECOVERY && !extraOpts?._recoveryAttempted) {
|
|
8726
8764
|
try {
|
|
8727
|
-
const { attemptRecovery } = await import("./recovery-
|
|
8765
|
+
const { attemptRecovery } = await import("./recovery-IMZUGWSS.js");
|
|
8728
8766
|
const recovery = await attemptRecovery(
|
|
8729
8767
|
session.page,
|
|
8730
8768
|
sessionName,
|
|
@@ -8840,7 +8878,15 @@ async function executeChain(input, options) {
|
|
|
8840
8878
|
continue;
|
|
8841
8879
|
}
|
|
8842
8880
|
const pluginArgs = cmdArgs.slice(1);
|
|
8843
|
-
|
|
8881
|
+
let pluginParams = parsePluginParams(pluginArgs, cmdEntry.parameters);
|
|
8882
|
+
if (cmdEntry.parameters) {
|
|
8883
|
+
const schemaCheck = cmdEntry.parameters.safeParse(pluginParams);
|
|
8884
|
+
if (schemaCheck.success) {
|
|
8885
|
+
for (const [k, v] of Object.entries(schemaCheck.data)) {
|
|
8886
|
+
if (!(k in pluginParams)) pluginParams[k] = v;
|
|
8887
|
+
}
|
|
8888
|
+
}
|
|
8889
|
+
}
|
|
8844
8890
|
const pluginCtx = {
|
|
8845
8891
|
args: pluginArgs,
|
|
8846
8892
|
options: pluginParams,
|
|
@@ -251,6 +251,7 @@ var CaptchaDetector = class {
|
|
|
251
251
|
};
|
|
252
252
|
|
|
253
253
|
// src/webhook.ts
|
|
254
|
+
import * as os from "os";
|
|
254
255
|
var WebhookNotifier = class {
|
|
255
256
|
url;
|
|
256
257
|
constructor(url) {
|
|
@@ -264,11 +265,15 @@ var WebhookNotifier = class {
|
|
|
264
265
|
*/
|
|
265
266
|
async notify(payload) {
|
|
266
267
|
if (!this.url) return false;
|
|
268
|
+
if (this.isDrelEndpoint(this.url)) {
|
|
269
|
+
payload = { ...payload, previewUrl: this.lanify(payload.previewUrl) };
|
|
270
|
+
}
|
|
267
271
|
try {
|
|
272
|
+
const body = this.isDrelEndpoint(this.url) ? JSON.stringify(this.toDrelPayload(payload)) : JSON.stringify(payload);
|
|
268
273
|
const response = await fetch(this.url, {
|
|
269
274
|
method: "POST",
|
|
270
275
|
headers: { "Content-Type": "application/json" },
|
|
271
|
-
body
|
|
276
|
+
body,
|
|
272
277
|
signal: AbortSignal.timeout(5e3)
|
|
273
278
|
});
|
|
274
279
|
return response.ok;
|
|
@@ -276,6 +281,39 @@ var WebhookNotifier = class {
|
|
|
276
281
|
return false;
|
|
277
282
|
}
|
|
278
283
|
}
|
|
284
|
+
/**
|
|
285
|
+
* previewUrl 里的 localhost/127.0.0.1 替换为本机局域网 IPv4——
|
|
286
|
+
* 推送目标是手机,localhost 在手机上不可达;同一 Wi-Fi 下局域网 IP 可达。
|
|
287
|
+
*/
|
|
288
|
+
lanify(url) {
|
|
289
|
+
if (!url) return url;
|
|
290
|
+
if (!url.includes("localhost") && !url.includes("127.0.0.1")) return url;
|
|
291
|
+
const ips = Object.values(os.networkInterfaces()).flat().filter((i) => !!i && i.family === "IPv4" && !i.internal);
|
|
292
|
+
if (!ips.length) return url;
|
|
293
|
+
return url.replace("localhost", ips[0].address).replace("127.0.0.1", ips[0].address);
|
|
294
|
+
}
|
|
295
|
+
/** Drel 推送端点识别(api.drel.app/<token>) */
|
|
296
|
+
isDrelEndpoint(url) {
|
|
297
|
+
return url.includes("api.drel.app") || url.includes("drel.app");
|
|
298
|
+
}
|
|
299
|
+
toDrelPayload(payload) {
|
|
300
|
+
const eventLabel = {
|
|
301
|
+
"captcha-detected": "\u26A0\uFE0F \u68C0\u6D4B\u5230\u9A8C\u8BC1\u7801",
|
|
302
|
+
"captcha-resolved": "\u2705 \u9A8C\u8BC1\u7801\u5DF2\u89E3\u51B3",
|
|
303
|
+
"session-started": "\u25B6\uFE0F \u4F1A\u8BDD\u5F00\u59CB",
|
|
304
|
+
"session-ended": "\u23F9 \u4F1A\u8BDD\u7ED3\u675F"
|
|
305
|
+
};
|
|
306
|
+
const parts = [];
|
|
307
|
+
if (payload.reason) parts.push(payload.reason);
|
|
308
|
+
if (payload.targetUrl) parts.push(`\u76EE\u6807: ${payload.targetUrl}`);
|
|
309
|
+
if (payload.sessionId) parts.push(`\u4F1A\u8BDD: ${payload.sessionId}`);
|
|
310
|
+
if (payload.timeout) parts.push(`\u8D85\u65F6: ${payload.timeout}s`);
|
|
311
|
+
return {
|
|
312
|
+
title: payload.title || `${eventLabel[payload.event] || payload.event} \u2014 \u9700\u8981\u4F60\u4ECB\u5165`,
|
|
313
|
+
body: payload.body || parts.join("\n") || payload.event,
|
|
314
|
+
...payload.previewUrl ? { url: payload.previewUrl } : {}
|
|
315
|
+
};
|
|
316
|
+
}
|
|
279
317
|
};
|
|
280
318
|
|
|
281
319
|
// src/human-interaction.ts
|
|
@@ -111,6 +111,7 @@ var CaptchaDetector = class {
|
|
|
111
111
|
};
|
|
112
112
|
|
|
113
113
|
// src/webhook.ts
|
|
114
|
+
import * as os from "os";
|
|
114
115
|
var WebhookNotifier = class {
|
|
115
116
|
url;
|
|
116
117
|
constructor(url) {
|
|
@@ -124,11 +125,15 @@ var WebhookNotifier = class {
|
|
|
124
125
|
*/
|
|
125
126
|
async notify(payload) {
|
|
126
127
|
if (!this.url) return false;
|
|
128
|
+
if (this.isDrelEndpoint(this.url)) {
|
|
129
|
+
payload = { ...payload, previewUrl: this.lanify(payload.previewUrl) };
|
|
130
|
+
}
|
|
127
131
|
try {
|
|
132
|
+
const body = this.isDrelEndpoint(this.url) ? JSON.stringify(this.toDrelPayload(payload)) : JSON.stringify(payload);
|
|
128
133
|
const response = await fetch(this.url, {
|
|
129
134
|
method: "POST",
|
|
130
135
|
headers: { "Content-Type": "application/json" },
|
|
131
|
-
body
|
|
136
|
+
body,
|
|
132
137
|
signal: AbortSignal.timeout(5e3)
|
|
133
138
|
});
|
|
134
139
|
return response.ok;
|
|
@@ -136,6 +141,39 @@ var WebhookNotifier = class {
|
|
|
136
141
|
return false;
|
|
137
142
|
}
|
|
138
143
|
}
|
|
144
|
+
/**
|
|
145
|
+
* previewUrl 里的 localhost/127.0.0.1 替换为本机局域网 IPv4——
|
|
146
|
+
* 推送目标是手机,localhost 在手机上不可达;同一 Wi-Fi 下局域网 IP 可达。
|
|
147
|
+
*/
|
|
148
|
+
lanify(url) {
|
|
149
|
+
if (!url) return url;
|
|
150
|
+
if (!url.includes("localhost") && !url.includes("127.0.0.1")) return url;
|
|
151
|
+
const ips = Object.values(os.networkInterfaces()).flat().filter((i) => !!i && i.family === "IPv4" && !i.internal);
|
|
152
|
+
if (!ips.length) return url;
|
|
153
|
+
return url.replace("localhost", ips[0].address).replace("127.0.0.1", ips[0].address);
|
|
154
|
+
}
|
|
155
|
+
/** Drel 推送端点识别(api.drel.app/<token>) */
|
|
156
|
+
isDrelEndpoint(url) {
|
|
157
|
+
return url.includes("api.drel.app") || url.includes("drel.app");
|
|
158
|
+
}
|
|
159
|
+
toDrelPayload(payload) {
|
|
160
|
+
const eventLabel = {
|
|
161
|
+
"captcha-detected": "\u26A0\uFE0F \u68C0\u6D4B\u5230\u9A8C\u8BC1\u7801",
|
|
162
|
+
"captcha-resolved": "\u2705 \u9A8C\u8BC1\u7801\u5DF2\u89E3\u51B3",
|
|
163
|
+
"session-started": "\u25B6\uFE0F \u4F1A\u8BDD\u5F00\u59CB",
|
|
164
|
+
"session-ended": "\u23F9 \u4F1A\u8BDD\u7ED3\u675F"
|
|
165
|
+
};
|
|
166
|
+
const parts = [];
|
|
167
|
+
if (payload.reason) parts.push(payload.reason);
|
|
168
|
+
if (payload.targetUrl) parts.push(`\u76EE\u6807: ${payload.targetUrl}`);
|
|
169
|
+
if (payload.sessionId) parts.push(`\u4F1A\u8BDD: ${payload.sessionId}`);
|
|
170
|
+
if (payload.timeout) parts.push(`\u8D85\u65F6: ${payload.timeout}s`);
|
|
171
|
+
return {
|
|
172
|
+
title: payload.title || `${eventLabel[payload.event] || payload.event} \u2014 \u9700\u8981\u4F60\u4ECB\u5165`,
|
|
173
|
+
body: payload.body || parts.join("\n") || payload.event,
|
|
174
|
+
...payload.previewUrl ? { url: payload.previewUrl } : {}
|
|
175
|
+
};
|
|
176
|
+
}
|
|
139
177
|
};
|
|
140
178
|
|
|
141
179
|
// src/human-interaction.ts
|
|
@@ -251,6 +251,7 @@ var CaptchaDetector = class {
|
|
|
251
251
|
};
|
|
252
252
|
|
|
253
253
|
// src/webhook.ts
|
|
254
|
+
import * as os from "os";
|
|
254
255
|
var WebhookNotifier = class {
|
|
255
256
|
url;
|
|
256
257
|
constructor(url) {
|
|
@@ -264,11 +265,15 @@ var WebhookNotifier = class {
|
|
|
264
265
|
*/
|
|
265
266
|
async notify(payload) {
|
|
266
267
|
if (!this.url) return false;
|
|
268
|
+
if (this.isDrelEndpoint(this.url)) {
|
|
269
|
+
payload = { ...payload, previewUrl: this.lanify(payload.previewUrl) };
|
|
270
|
+
}
|
|
267
271
|
try {
|
|
272
|
+
const body = this.isDrelEndpoint(this.url) ? JSON.stringify(this.toDrelPayload(payload)) : JSON.stringify(payload);
|
|
268
273
|
const response = await fetch(this.url, {
|
|
269
274
|
method: "POST",
|
|
270
275
|
headers: { "Content-Type": "application/json" },
|
|
271
|
-
body
|
|
276
|
+
body,
|
|
272
277
|
signal: AbortSignal.timeout(5e3)
|
|
273
278
|
});
|
|
274
279
|
return response.ok;
|
|
@@ -276,6 +281,39 @@ var WebhookNotifier = class {
|
|
|
276
281
|
return false;
|
|
277
282
|
}
|
|
278
283
|
}
|
|
284
|
+
/**
|
|
285
|
+
* previewUrl 里的 localhost/127.0.0.1 替换为本机局域网 IPv4——
|
|
286
|
+
* 推送目标是手机,localhost 在手机上不可达;同一 Wi-Fi 下局域网 IP 可达。
|
|
287
|
+
*/
|
|
288
|
+
lanify(url) {
|
|
289
|
+
if (!url) return url;
|
|
290
|
+
if (!url.includes("localhost") && !url.includes("127.0.0.1")) return url;
|
|
291
|
+
const ips = Object.values(os.networkInterfaces()).flat().filter((i) => !!i && i.family === "IPv4" && !i.internal);
|
|
292
|
+
if (!ips.length) return url;
|
|
293
|
+
return url.replace("localhost", ips[0].address).replace("127.0.0.1", ips[0].address);
|
|
294
|
+
}
|
|
295
|
+
/** Drel 推送端点识别(api.drel.app/<token>) */
|
|
296
|
+
isDrelEndpoint(url) {
|
|
297
|
+
return url.includes("api.drel.app") || url.includes("drel.app");
|
|
298
|
+
}
|
|
299
|
+
toDrelPayload(payload) {
|
|
300
|
+
const eventLabel = {
|
|
301
|
+
"captcha-detected": "\u26A0\uFE0F \u68C0\u6D4B\u5230\u9A8C\u8BC1\u7801",
|
|
302
|
+
"captcha-resolved": "\u2705 \u9A8C\u8BC1\u7801\u5DF2\u89E3\u51B3",
|
|
303
|
+
"session-started": "\u25B6\uFE0F \u4F1A\u8BDD\u5F00\u59CB",
|
|
304
|
+
"session-ended": "\u23F9 \u4F1A\u8BDD\u7ED3\u675F"
|
|
305
|
+
};
|
|
306
|
+
const parts = [];
|
|
307
|
+
if (payload.reason) parts.push(payload.reason);
|
|
308
|
+
if (payload.targetUrl) parts.push(`\u76EE\u6807: ${payload.targetUrl}`);
|
|
309
|
+
if (payload.sessionId) parts.push(`\u4F1A\u8BDD: ${payload.sessionId}`);
|
|
310
|
+
if (payload.timeout) parts.push(`\u8D85\u65F6: ${payload.timeout}s`);
|
|
311
|
+
return {
|
|
312
|
+
title: payload.title || `${eventLabel[payload.event] || payload.event} \u2014 \u9700\u8981\u4F60\u4ECB\u5165`,
|
|
313
|
+
body: payload.body || parts.join("\n") || payload.event,
|
|
314
|
+
...payload.previewUrl ? { url: payload.previewUrl } : {}
|
|
315
|
+
};
|
|
316
|
+
}
|
|
279
317
|
};
|
|
280
318
|
|
|
281
319
|
// src/human-interaction.ts
|
package/dist/cli.js
CHANGED
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
loadHooks,
|
|
17
17
|
parsePluginParams,
|
|
18
18
|
readJsonFile
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-A7RZYSWM.js";
|
|
20
20
|
import "./chunk-JKVUFP3G.js";
|
|
21
21
|
import {
|
|
22
22
|
forwardCommandLog,
|
|
@@ -5957,6 +5957,14 @@ Run "xbrowser ${command} ${subCommand} --help" to see available parameters.`
|
|
|
5957
5957
|
if (options.target && !params._target) {
|
|
5958
5958
|
params._target = options.target;
|
|
5959
5959
|
}
|
|
5960
|
+
if (cmdEntry.parameters) {
|
|
5961
|
+
const schemaFill = cmdEntry.parameters.safeParse(params);
|
|
5962
|
+
if (schemaFill.success) {
|
|
5963
|
+
for (const [k, v] of Object.entries(schemaFill.data)) {
|
|
5964
|
+
if (!(k in params)) params[k] = v;
|
|
5965
|
+
}
|
|
5966
|
+
}
|
|
5967
|
+
}
|
|
5960
5968
|
const needsBrowser = cmdEntry.scope === "page" || cmdEntry.scope === "browser";
|
|
5961
5969
|
if (needsBrowser && !process.env.XBROWSER_DAEMON_WORKER) {
|
|
5962
5970
|
const { forwardPluginExec } = await import("./daemon-client-HEGAXWGK.js");
|
|
@@ -6264,7 +6272,7 @@ async function handleRemote(args, options, mode) {
|
|
|
6264
6272
|
}
|
|
6265
6273
|
}
|
|
6266
6274
|
async function startMcpServer() {
|
|
6267
|
-
const { startMcpStdio } = await import("./server-
|
|
6275
|
+
const { startMcpStdio } = await import("./server-YVMWMNHD.js");
|
|
6268
6276
|
startMcpStdio();
|
|
6269
6277
|
}
|
|
6270
6278
|
|
package/dist/daemon-main.js
CHANGED
|
@@ -69,6 +69,35 @@ import {
|
|
|
69
69
|
removeSession
|
|
70
70
|
} from "@dyyz1993/xcli-core";
|
|
71
71
|
|
|
72
|
+
// src/fail-codes.ts
|
|
73
|
+
var FAIL_CODES = {
|
|
74
|
+
selectorNotFound: "RETRYABLE: selector-not-found",
|
|
75
|
+
pageNotReady: "RETRYABLE: page-not-ready",
|
|
76
|
+
elementGone: "RETRYABLE: element-gone",
|
|
77
|
+
captcha: "HUMAN: captcha",
|
|
78
|
+
loginWall: "HUMAN: login-wall",
|
|
79
|
+
permissionDenied: "FATAL: permission-denied",
|
|
80
|
+
targetGone: "FATAL: target-gone",
|
|
81
|
+
unknown: "FATAL: unknown"
|
|
82
|
+
};
|
|
83
|
+
function classifyFailure(message) {
|
|
84
|
+
const msg = (message || "").toLowerCase();
|
|
85
|
+
const pick = (code) => ({
|
|
86
|
+
code,
|
|
87
|
+
level: code.split(":")[0],
|
|
88
|
+
message: message || code
|
|
89
|
+
});
|
|
90
|
+
if (/captcha|验证码|人机校验|recaptcha|hcaptcha|turnstile/.test(msg)) return pick(FAIL_CODES.captcha);
|
|
91
|
+
if (/login|登录|signin|sign in|未登录|login-required|LOGIN_REQUIRED/.test(msg)) return pick(FAIL_CODES.loginWall);
|
|
92
|
+
if (/permission|权限|not allowed|forbidden|EACCES/.test(msg)) return pick(FAIL_CODES.permissionDenied);
|
|
93
|
+
if (/selector|选择器|element not found|未找到元素|no such element|queryselector returned null/.test(msg))
|
|
94
|
+
return pick(FAIL_CODES.selectorNotFound);
|
|
95
|
+
if (/timeout|超时|timed out|waiting for/.test(msg)) return pick(FAIL_CODES.pageNotReady);
|
|
96
|
+
if (/net::err|404|navigation failed|页面不存在|target closed|element-gone/.test(msg))
|
|
97
|
+
return pick(FAIL_CODES.targetGone);
|
|
98
|
+
return pick(FAIL_CODES.unknown);
|
|
99
|
+
}
|
|
100
|
+
|
|
72
101
|
// src/executor.ts
|
|
73
102
|
import {
|
|
74
103
|
ok as ok30,
|
|
@@ -7966,7 +7995,16 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
7966
7995
|
if (isSuccess) {
|
|
7967
7996
|
return { ...ok30(raw.data, merged.length > 0 ? merged : raw.tips), duration, ...hookOutputs ? { hookOutputs } : {} };
|
|
7968
7997
|
}
|
|
7969
|
-
|
|
7998
|
+
const fc = classifyFailure(raw.message);
|
|
7999
|
+
return {
|
|
8000
|
+
success: false,
|
|
8001
|
+
data: raw.data,
|
|
8002
|
+
message: raw.message,
|
|
8003
|
+
tips: mergedOrRaw,
|
|
8004
|
+
duration,
|
|
8005
|
+
error: { code: fc.code, level: fc.level },
|
|
8006
|
+
...hookOutputs ? { hookOutputs } : {}
|
|
8007
|
+
};
|
|
7970
8008
|
}
|
|
7971
8009
|
const smartTipNormalized = normalizeTips6(smartTips);
|
|
7972
8010
|
recordArchive(session?.id, sessionName, {
|
|
@@ -7985,7 +8023,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
7985
8023
|
const errorMessage = errMsg(err);
|
|
7986
8024
|
if (session?.page && process.env.XBROWSER_RECOVERY && !extraOpts?._recoveryAttempted) {
|
|
7987
8025
|
try {
|
|
7988
|
-
const { attemptRecovery } = await import("./recovery-
|
|
8026
|
+
const { attemptRecovery } = await import("./recovery-5C4FBA5O.js");
|
|
7989
8027
|
const recovery = await attemptRecovery(
|
|
7990
8028
|
session.page,
|
|
7991
8029
|
sessionName,
|
|
@@ -8101,7 +8139,15 @@ async function executeChain(input, options) {
|
|
|
8101
8139
|
continue;
|
|
8102
8140
|
}
|
|
8103
8141
|
const pluginArgs = cmdArgs.slice(1);
|
|
8104
|
-
|
|
8142
|
+
let pluginParams = parsePluginParams(pluginArgs, cmdEntry.parameters);
|
|
8143
|
+
if (cmdEntry.parameters) {
|
|
8144
|
+
const schemaCheck = cmdEntry.parameters.safeParse(pluginParams);
|
|
8145
|
+
if (schemaCheck.success) {
|
|
8146
|
+
for (const [k, v] of Object.entries(schemaCheck.data)) {
|
|
8147
|
+
if (!(k in pluginParams)) pluginParams[k] = v;
|
|
8148
|
+
}
|
|
8149
|
+
}
|
|
8150
|
+
}
|
|
8105
8151
|
const pluginCtx = {
|
|
8106
8152
|
args: pluginArgs,
|
|
8107
8153
|
options: pluginParams,
|
|
@@ -9025,7 +9071,7 @@ function createRPCHandler() {
|
|
|
9025
9071
|
return result;
|
|
9026
9072
|
} catch (err) {
|
|
9027
9073
|
const errorMessage = errMsg(err);
|
|
9028
|
-
const { attemptRecovery } = await import("./recovery-
|
|
9074
|
+
const { attemptRecovery } = await import("./recovery-5C4FBA5O.js");
|
|
9029
9075
|
const recovery = await attemptRecovery(
|
|
9030
9076
|
session?.page,
|
|
9031
9077
|
sessionName,
|
package/dist/index.d.ts
CHANGED
|
@@ -836,6 +836,10 @@ interface ExecutionResult {
|
|
|
836
836
|
duration: number;
|
|
837
837
|
tips?: Tip[];
|
|
838
838
|
hookOutputs?: HookOutput[];
|
|
839
|
+
error?: {
|
|
840
|
+
code: string;
|
|
841
|
+
level: string;
|
|
842
|
+
};
|
|
839
843
|
}
|
|
840
844
|
/**
|
|
841
845
|
* Result of a single step within a command chain execution.
|
|
@@ -2488,6 +2492,10 @@ interface WebhookPayload {
|
|
|
2488
2492
|
previewUrl?: string;
|
|
2489
2493
|
targetUrl?: string;
|
|
2490
2494
|
timeout?: number;
|
|
2495
|
+
/** Drel 等推送服务的通知标题(可选;缺省由 event 推导) */
|
|
2496
|
+
title?: string;
|
|
2497
|
+
/** Drel 等推送服务的通知正文(可选;缺省由其余字段拼装) */
|
|
2498
|
+
body?: string;
|
|
2491
2499
|
}
|
|
2492
2500
|
/**
|
|
2493
2501
|
* Sends webhook notifications for browser automation lifecycle events.
|
|
@@ -2505,6 +2513,14 @@ declare class WebhookNotifier {
|
|
|
2505
2513
|
* @returns `true` if the request succeeded (HTTP 2xx), `false` otherwise.
|
|
2506
2514
|
*/
|
|
2507
2515
|
notify(payload: WebhookPayload): Promise<boolean>;
|
|
2516
|
+
/**
|
|
2517
|
+
* previewUrl 里的 localhost/127.0.0.1 替换为本机局域网 IPv4——
|
|
2518
|
+
* 推送目标是手机,localhost 在手机上不可达;同一 Wi-Fi 下局域网 IP 可达。
|
|
2519
|
+
*/
|
|
2520
|
+
private lanify;
|
|
2521
|
+
/** Drel 推送端点识别(api.drel.app/<token>) */
|
|
2522
|
+
private isDrelEndpoint;
|
|
2523
|
+
private toDrelPayload;
|
|
2508
2524
|
}
|
|
2509
2525
|
|
|
2510
2526
|
interface CaptchaConfig {
|
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-BROEUPL3.js";
|
|
52
52
|
import {
|
|
53
53
|
SessionRecorder
|
|
54
54
|
} from "./chunk-IR6C24P7.js";
|
|
@@ -97,7 +97,7 @@ import {
|
|
|
97
97
|
HumanInteractionManager,
|
|
98
98
|
ScreencastCapturer,
|
|
99
99
|
WebhookNotifier
|
|
100
|
-
} from "./chunk-
|
|
100
|
+
} from "./chunk-SOPN4XDK.js";
|
|
101
101
|
import {
|
|
102
102
|
NPM_REGISTRY_URL,
|
|
103
103
|
NPM_SCOPE,
|
|
@@ -6053,6 +6053,14 @@ Run "xbrowser ${command} ${subCommand} --help" to see available parameters.`
|
|
|
6053
6053
|
if (options.target && !params._target) {
|
|
6054
6054
|
params._target = options.target;
|
|
6055
6055
|
}
|
|
6056
|
+
if (cmdEntry.parameters) {
|
|
6057
|
+
const schemaFill = cmdEntry.parameters.safeParse(params);
|
|
6058
|
+
if (schemaFill.success) {
|
|
6059
|
+
for (const [k, v] of Object.entries(schemaFill.data)) {
|
|
6060
|
+
if (!(k in params)) params[k] = v;
|
|
6061
|
+
}
|
|
6062
|
+
}
|
|
6063
|
+
}
|
|
6056
6064
|
const needsBrowser = cmdEntry.scope === "page" || cmdEntry.scope === "browser";
|
|
6057
6065
|
if (needsBrowser && !process.env.XBROWSER_DAEMON_WORKER) {
|
|
6058
6066
|
const { forwardPluginExec } = await import("./daemon-client-HEGAXWGK.js");
|
|
@@ -6360,7 +6368,7 @@ async function handleRemote(args, options, mode) {
|
|
|
6360
6368
|
}
|
|
6361
6369
|
}
|
|
6362
6370
|
async function startMcpServer() {
|
|
6363
|
-
const { startMcpStdio } = await import("./server-
|
|
6371
|
+
const { startMcpStdio } = await import("./server-NPINOVJS.js");
|
|
6364
6372
|
startMcpStdio();
|
|
6365
6373
|
}
|
|
6366
6374
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
import {
|
|
2
|
+
HumanInteractionManager
|
|
3
|
+
} from "./chunk-FNXA6FDM.js";
|
|
1
4
|
import {
|
|
2
5
|
buildViewerUrl
|
|
3
6
|
} from "./chunk-3OD76SUE.js";
|
|
4
|
-
import {
|
|
5
|
-
HumanInteractionManager
|
|
6
|
-
} from "./chunk-W44UXZSC.js";
|
|
7
7
|
import "./chunk-ZTHE5RBZ.js";
|
|
8
8
|
import "./chunk-Q2DFJQGS.js";
|
|
9
9
|
import "./chunk-KFQGP6VL.js";
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
HumanInteractionManager
|
|
3
|
-
} from "./chunk-LPW6QS4M.js";
|
|
4
1
|
import {
|
|
5
2
|
buildViewerUrl
|
|
6
3
|
} from "./chunk-3OD76SUE.js";
|
|
4
|
+
import {
|
|
5
|
+
HumanInteractionManager
|
|
6
|
+
} from "./chunk-SOPN4XDK.js";
|
|
7
7
|
import "./chunk-ZTHE5RBZ.js";
|
|
8
8
|
import "./chunk-Q2DFJQGS.js";
|
|
9
9
|
import "./chunk-KFQGP6VL.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xbrowser/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.24.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": {
|
|
@@ -130,4 +130,4 @@
|
|
|
130
130
|
"optionalDependencies": {
|
|
131
131
|
"undici": "^7.29.1"
|
|
132
132
|
}
|
|
133
|
-
}
|
|
133
|
+
}
|