@xbrowser/cli 1.9.5 → 1.9.6

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/index.js CHANGED
@@ -44,7 +44,7 @@ import {
44
44
  startDaemonProcess,
45
45
  stopDaemonProcess,
46
46
  version
47
- } from "./chunk-53LRNYPG.js";
47
+ } from "./chunk-YAUG3NSI.js";
48
48
  import {
49
49
  generateBashScript,
50
50
  generateJSScript,
@@ -61,7 +61,7 @@ import {
61
61
  } from "./chunk-ANVL2ID2.js";
62
62
  import {
63
63
  SessionRecorder
64
- } from "./chunk-YSCY52UJ.js";
64
+ } from "./chunk-CH5ZQV64.js";
65
65
  import {
66
66
  addKnownIssue,
67
67
  getKnowledgePath,
@@ -86,7 +86,7 @@ import {
86
86
  resolveLaunchOpts,
87
87
  saveSessionDiskMeta,
88
88
  setActivePage
89
- } from "./chunk-MSQIPXKE.js";
89
+ } from "./chunk-5JSOGINQ.js";
90
90
  import "./chunk-VJNMAWPZ.js";
91
91
  import "./chunk-TNEN6VQ2.js";
92
92
  import {
@@ -7406,7 +7406,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
7406
7406
  }
7407
7407
  let targetPageOverride = null;
7408
7408
  if (_target && extraOpts?.cdpEndpoint) {
7409
- const { findTargetPage } = await import("./browser-C63PFGPT.js");
7409
+ const { findTargetPage } = await import("./browser-ZPQP6VW7.js");
7410
7410
  targetPageOverride = await findTargetPage(extraOpts.cdpEndpoint, _target);
7411
7411
  if (!targetPageOverride) {
7412
7412
  return errorResult(`Target "${_target}" not found. Use 'xbrowser targets --cdp ${extraOpts.cdpEndpoint}' to list available pages.`);
@@ -7423,7 +7423,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
7423
7423
  params = result.data;
7424
7424
  }
7425
7425
  if (command.scope !== "cli" && !process.env.XBROWSER_DAEMON_WORKER) {
7426
- const { forwardExec } = await import("./daemon-client-TOUDMIY5.js");
7426
+ const { forwardExec } = await import("./daemon-client-CMRAWXTN.js");
7427
7427
  const result = await forwardExec(commandName, params, sessionName, extraOpts?.cdpEndpoint);
7428
7428
  if (result) return result;
7429
7429
  }
@@ -8868,7 +8868,7 @@ var PluginInstaller = class {
8868
8868
  import { outputFormatter, OutputFormatter } from "@dyyz1993/xcli-core";
8869
8869
  var formatter = new OutputFormatter();
8870
8870
  function outputResult(result, mode = "text") {
8871
- if (typeof result === "object" && result !== null) {
8871
+ if (mode !== "json" && mode !== "yaml" && typeof result === "object" && result !== null) {
8872
8872
  const r = result;
8873
8873
  if (r.success === false) {
8874
8874
  outputError(r.message ? String(r.message) : "Unknown error");
@@ -10882,7 +10882,7 @@ async function handlePlugin(args, options, mode) {
10882
10882
  } catch {
10883
10883
  }
10884
10884
  try {
10885
- const { daemonPing } = await import("./daemon-client-TOUDMIY5.js");
10885
+ const { daemonPing } = await import("./daemon-client-CMRAWXTN.js");
10886
10886
  if (await daemonPing()) {
10887
10887
  await fetch("http://localhost:9224/rpc", {
10888
10888
  method: "POST",
@@ -11040,7 +11040,8 @@ async function handleRecord(args, options, mode) {
11040
11040
  const url = options.url;
11041
11041
  const sessionName = options.session || "default";
11042
11042
  const cdpEndpoint = options.cdp;
11043
- const result = await forwardRecordStart(sessionName, url, cdpEndpoint);
11043
+ const stream = options.stream || "clean";
11044
+ const result = await forwardRecordStart(sessionName, url, cdpEndpoint, stream);
11044
11045
  if (!result.ok) {
11045
11046
  outputError(String(result.error || "Failed to start recording"));
11046
11047
  return;
@@ -11335,7 +11336,15 @@ async function handleReplay(args, options, mode) {
11335
11336
  const absPath = await import("path").then((p) => p.resolve(filePath));
11336
11337
  const result = await forwardReplay(absPath, sessionName, slowMo);
11337
11338
  if (!result.ok) {
11338
- outputError(String(result.errors ? result.errors.map((e) => e.error).join("; ") : result.error || "Replay failed"));
11339
+ if (mode === "json" || mode === "yaml") {
11340
+ outputResult({
11341
+ ...result,
11342
+ success: false,
11343
+ message: result.errors ? result.errors.map((e) => e.error).join("; ") : result.error || "Replay failed"
11344
+ }, mode);
11345
+ } else {
11346
+ outputError(String(result.errors ? result.errors.map((e) => e.error).join("; ") : result.error || "Replay failed"));
11347
+ }
11339
11348
  return;
11340
11349
  }
11341
11350
  outputResult(result, mode);
@@ -11439,7 +11448,7 @@ async function handleFilter(args, _mode, options) {
11439
11448
  }
11440
11449
  }
11441
11450
  async function handleGeneratePlugin(sessionName, pluginName, outputDir) {
11442
- const { SessionRecorder: SessionRecorder2 } = await import("./session-recorder-KPU4YQAE.js");
11451
+ const { SessionRecorder: SessionRecorder2 } = await import("./session-recorder-RFOPX4SF.js");
11443
11452
  const { readSiteKnowledge: readSiteKnowledge2, toMarkdown } = await import("./site-knowledge-SYC6VCDB.js");
11444
11453
  const { mkdirSync: mkdirSync11, writeFileSync: writeFileSync13 } = await import("fs");
11445
11454
  const { join: join14 } = await import("path");
@@ -13376,7 +13385,7 @@ Run "xbrowser ${command} ${subCommand} --help" to see available parameters.`
13376
13385
  }
13377
13386
  const needsBrowser = cmdEntry.scope === "page" || cmdEntry.scope === "browser";
13378
13387
  if (needsBrowser && !process.env.XBROWSER_DAEMON_WORKER) {
13379
- const { forwardExec } = await import("./daemon-client-TOUDMIY5.js");
13388
+ const { forwardExec } = await import("./daemon-client-CMRAWXTN.js");
13380
13389
  const userTimeout = typeof params.timeout === "number" && params.timeout > 0 ? params.timeout * 1e3 + 3e4 : void 0;
13381
13390
  const result = await forwardExec(`${command}.${subCommand}`, params, sessionName, cdpEndpoint, userTimeout);
13382
13391
  const resultData = result && typeof result === "object" && "data" in result ? result.data : void 0;
@@ -13416,7 +13425,7 @@ Run "xbrowser ${command} ${subCommand} --help" to see available parameters.`
13416
13425
  const targetPage = pages[cmdTabIndex];
13417
13426
  await targetPage.bringToFront().catch(() => {
13418
13427
  });
13419
- const { setActivePage: setActivePage2 } = await import("./browser-C63PFGPT.js");
13428
+ const { setActivePage: setActivePage2 } = await import("./browser-ZPQP6VW7.js");
13420
13429
  setActivePage2(session, targetPage);
13421
13430
  }
13422
13431
  }
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  SessionRecorder
3
- } from "./chunk-TTZNR3QP.js";
3
+ } from "./chunk-C4O73DQL.js";
4
4
  import "./chunk-KFQGP6VL.js";
5
5
  export {
6
6
  SessionRecorder
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  SessionRecorder
3
- } from "./chunk-YSCY52UJ.js";
3
+ } from "./chunk-CH5ZQV64.js";
4
4
  import "./chunk-OZKD3W4X.js";
5
5
  import "./chunk-KFQGP6VL.js";
6
6
  export {
@@ -56,15 +56,17 @@ var SessionReplayer = class {
56
56
  await this.replayTrajectory(action.trajectory);
57
57
  }
58
58
  await this.replayAction(action);
59
+ if (action.type !== "resize" && action.type !== "clipboard" && action.type !== "visibility") {
60
+ try {
61
+ await this.page.waitForLoadState("domcontentloaded", this.opts.stepTimeout);
62
+ } catch {
63
+ }
64
+ }
59
65
  success++;
60
66
  } catch (e) {
61
67
  const err = e instanceof Error ? e : new Error(String(e));
62
68
  this.opts.onError?.(action, err);
63
- if (action.type === "navigation") {
64
- skipped++;
65
- } else {
66
- failed++;
67
- }
69
+ failed++;
68
70
  }
69
71
  if (i < actions.length - 1) {
70
72
  await new Promise((r) => setTimeout(r, this.opts.stepDelay));
@@ -78,41 +80,36 @@ var SessionReplayer = class {
78
80
  const timeout = this.opts.stepTimeout;
79
81
  switch (action.type) {
80
82
  case "navigation":
81
- await page.goto(action.url, { waitUntil: "domcontentloaded", timeout });
83
+ await page.goto(action.url, { waitUntil: "load", timeout });
82
84
  break;
83
85
  case "goto":
84
- await page.goto(action.url, { waitUntil: "domcontentloaded", timeout });
86
+ await page.goto(action.url, { waitUntil: "load", timeout });
85
87
  break;
86
88
  case "click":
87
89
  case "cdp-click": {
88
- const selector = this.resolveSelector(action);
89
- await page.waitForSelector(selector, { state: "visible", timeout });
90
+ const selector = await this.resolveAndWait(action);
90
91
  await page.click(selector, { timeout });
91
92
  break;
92
93
  }
93
94
  case "input": {
94
- const selector = this.resolveSelector(action);
95
- await page.waitForSelector(selector, { state: "visible", timeout });
95
+ const selector = await this.resolveAndWait(action);
96
96
  await page.fill(selector, action.value ?? "", { timeout });
97
97
  break;
98
98
  }
99
99
  case "cdp-fill": {
100
- const selector = this.resolveSelector(action);
101
- await page.waitForSelector(selector, { state: "visible", timeout });
100
+ const selector = await this.resolveAndWait(action);
102
101
  await page.fill(selector, action.value ?? "", { timeout });
103
102
  break;
104
103
  }
105
104
  case "change": {
106
- const selector = this.resolveSelector(action);
107
- await page.waitForSelector(selector, { state: "visible", timeout });
105
+ const selector = await this.resolveAndWait(action);
108
106
  if (action.value) {
109
107
  await page.selectOption(selector, action.value);
110
108
  }
111
109
  break;
112
110
  }
113
111
  case "filechooser": {
114
- const selector = this.resolveSelector(action);
115
- await page.waitForSelector(selector, { state: "visible", timeout });
112
+ const selector = await this.resolveAndWait(action);
116
113
  const files = this.resolveFiles(action);
117
114
  if (files.length > 0) {
118
115
  await page.setInputFiles(selector, files);
@@ -155,11 +152,8 @@ var SessionReplayer = class {
155
152
  break;
156
153
  }
157
154
  case "hover": {
158
- const selector = this.resolveSelector(action);
159
- if (selector) {
160
- await page.waitForSelector(selector, { state: "visible", timeout });
161
- await page.hover(selector);
162
- }
155
+ const selector = await this.resolveAndWait(action);
156
+ await page.hover(selector);
163
157
  break;
164
158
  }
165
159
  case "drag": {
@@ -199,10 +193,9 @@ var SessionReplayer = class {
199
193
  break;
200
194
  }
201
195
  case "focus": {
202
- const selector = this.resolveSelector(action);
203
- if (selector && action.focus?.focusType === "focus") {
204
- await page.waitForSelector(selector, { state: "visible", timeout });
205
- await page.click(selector, { timeout });
196
+ const selector = await this.resolveAndWait(action);
197
+ if (action.focus?.focusType === "focus") {
198
+ await page.locator(selector).focus();
206
199
  }
207
200
  break;
208
201
  }
@@ -210,10 +203,16 @@ var SessionReplayer = class {
210
203
  break;
211
204
  }
212
205
  case "submit": {
213
- const selector = this.resolveSelector(action);
214
- if (selector) {
215
- await page.click(selector, { timeout });
216
- }
206
+ const selector = await this.resolveAndWait(action);
207
+ await page.evaluate((sel) => {
208
+ const form = document.querySelector(sel);
209
+ if (!form) return;
210
+ if (typeof form.requestSubmit === "function") {
211
+ form.requestSubmit();
212
+ } else {
213
+ form.dispatchEvent(new Event("submit", { bubbles: true, cancelable: true }));
214
+ }
215
+ }, selector);
217
216
  break;
218
217
  }
219
218
  case "scroll": {
@@ -239,12 +238,10 @@ var SessionReplayer = class {
239
238
  await page.mouse.move(x, y);
240
239
  }
241
240
  }
242
- /** Resolve the best selector for an action */
241
+ /** Resolve the best selector for an action (primary selector only) */
243
242
  resolveSelector(action) {
244
243
  const el = action.element;
245
244
  if (!el) return "";
246
- if (el.textFallback) {
247
- }
248
245
  if (el.selector) {
249
246
  return el.selector;
250
247
  }
@@ -253,6 +250,41 @@ var SessionReplayer = class {
253
250
  }
254
251
  return "";
255
252
  }
253
+ /**
254
+ * X2: Wait for an element using the best available selector, with
255
+ * confidence-based fallback support.
256
+ *
257
+ * Returns the first matching selector, or throws if none match.
258
+ * Fallback order:
259
+ * 1. Primary CSS selector (always tried first)
260
+ * 2. textFallback selector (for low-confidence selectors when primary fails)
261
+ * 3. Tag-based fallback (last resort)
262
+ */
263
+ async resolveAndWait(action) {
264
+ const el = action.element;
265
+ if (!el) throw new Error("No element metadata");
266
+ const page = this.page;
267
+ const timeout = this.opts.stepTimeout;
268
+ const candidates = [];
269
+ if (el.selector) candidates.push(el.selector);
270
+ if (el.confidence === "low" && el.textFallback?.selector) {
271
+ if (!candidates.includes(el.textFallback.selector)) {
272
+ candidates.push(el.textFallback.selector);
273
+ }
274
+ }
275
+ if (el.tag && !candidates.includes(el.tag)) {
276
+ candidates.push(el.tag);
277
+ }
278
+ if (candidates.length === 0) throw new Error("No selector available for element");
279
+ for (const sel of candidates) {
280
+ try {
281
+ await page.waitForSelector(sel, { state: "visible", timeout });
282
+ return sel;
283
+ } catch {
284
+ }
285
+ }
286
+ throw new Error(`Element not found, tried: ${candidates.join(", ")}`);
287
+ }
256
288
  /** Resolve file payloads from a filechooser action */
257
289
  resolveFiles(action) {
258
290
  if (!action.files?.fileData) return [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xbrowser/cli",
3
- "version": "1.9.5",
3
+ "version": "1.9.6",
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": {