@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.
@@ -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
- return { success: false, data: raw.data, message: raw.message, tips: mergedOrRaw, duration, ...hookOutputs ? { hookOutputs } : {} };
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-6RI2FE3E.js");
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
- const pluginParams = parsePluginParams(pluginArgs, cmdEntry.parameters);
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-DK6QGYNC.js");
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
- return { success: false, data: raw.data, message: raw.message, tips: mergedOrRaw, duration, ...hookOutputs ? { hookOutputs } : {} };
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-67DMH2OM.js");
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
- const pluginParams = parsePluginParams(pluginArgs, cmdEntry.parameters);
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: JSON.stringify(payload),
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: JSON.stringify(payload),
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: JSON.stringify(payload),
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-TBSS7ES4.js";
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-HU4F5KN7.js");
6275
+ const { startMcpStdio } = await import("./server-YVMWMNHD.js");
6268
6276
  startMcpStdio();
6269
6277
  }
6270
6278
 
@@ -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
- return { success: false, data: raw.data, message: raw.message, tips: mergedOrRaw, duration, ...hookOutputs ? { hookOutputs } : {} };
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-LGY25LIM.js");
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
- const pluginParams = parsePluginParams(pluginArgs, cmdEntry.parameters);
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-LGY25LIM.js");
9074
+ const { attemptRecovery } = await import("./recovery-5C4FBA5O.js");
9029
9075
  const recovery = await attemptRecovery(
9030
9076
  session?.page,
9031
9077
  sessionName,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  HumanInteractionManager
3
- } from "./chunk-FBQZ3PVD.js";
3
+ } from "./chunk-PGKV24QN.js";
4
4
  import "./chunk-HBMEFSTB.js";
5
5
  import "./chunk-KFQGP6VL.js";
6
6
  export {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  HumanInteractionManager
3
- } from "./chunk-LPW6QS4M.js";
3
+ } from "./chunk-SOPN4XDK.js";
4
4
  import "./chunk-ZTHE5RBZ.js";
5
5
  import "./chunk-KFQGP6VL.js";
6
6
  export {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  HumanInteractionManager
3
- } from "./chunk-W44UXZSC.js";
3
+ } from "./chunk-FNXA6FDM.js";
4
4
  import "./chunk-ZTHE5RBZ.js";
5
5
  import "./chunk-KFQGP6VL.js";
6
6
  export {
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-LFX24QJZ.js";
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-W44UXZSC.js";
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-DZUIBVVR.js");
6371
+ const { startMcpStdio } = await import("./server-NPINOVJS.js");
6364
6372
  startMcpStdio();
6365
6373
  }
6366
6374
 
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  HumanInteractionManager
3
- } from "./chunk-FBQZ3PVD.js";
3
+ } from "./chunk-PGKV24QN.js";
4
4
  import {
5
5
  buildViewerUrl
6
6
  } from "./chunk-TZPKFUBT.js";
@@ -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";
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  executeChain,
3
3
  executeCommand
4
- } from "./chunk-LFX24QJZ.js";
4
+ } from "./chunk-BROEUPL3.js";
5
5
  import "./chunk-2Q3GQRUP.js";
6
6
  import "./chunk-JKVUFP3G.js";
7
7
  import "./chunk-3OD76SUE.js";
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  executeChain,
3
3
  executeCommand
4
- } from "./chunk-TBSS7ES4.js";
4
+ } from "./chunk-A7RZYSWM.js";
5
5
  import "./chunk-JKVUFP3G.js";
6
6
  import "./chunk-3OD76SUE.js";
7
7
  import "./chunk-ZTHE5RBZ.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xbrowser/cli",
3
- "version": "1.23.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
+ }