@xbrowser/cli 1.4.6 → 1.4.8

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/README.md CHANGED
@@ -9,7 +9,7 @@
9
9
 
10
10
  ## 特性
11
11
 
12
- - **50 浏览器命令** — 导航、交互、查询、存储、截图,覆盖常见自动化场景
12
+ - **51 浏览器命令** — 导航、交互、查询、存储、截图,覆盖常见自动化场景
13
13
  - **命令链** — 用 `&&`、`,`、`+`、`->`、`;` 串联多个命令,一行搞定复杂流程
14
14
  - **管道 & Heredoc** — 支持 stdin 管道和 heredoc 批量执行
15
15
  - **录制 / 回放** — 录制浏览器操作为 YAML,随时回放,可转换为 JS/Python/Bash 脚本
@@ -854,7 +854,7 @@ npm run validate
854
854
  ```
855
855
  xbrowser/
856
856
  ├── src/
857
- │ ├── commands/ # 50 个浏览器命令定义
857
+ │ ├── commands/ # 51 个浏览器命令定义
858
858
  │ ├── builtins/ # CLI 内置命令(config, plugin, session, create)
859
859
  │ ├── recorder/ # 录制引擎(录制器 + 回放器)
860
860
  │ ├── session/ # 会话管理
@@ -883,7 +883,7 @@ xbrowser/
883
883
  | **Setup** | `npm i -g` — 0 config | Install + browser download | Install + browser download | Install + WebDriver + drivers |
884
884
  | **Web Scraping** | Built-in (`scrape`, `crawl`, `map`) | Write custom scripts | Write custom scripts | Write custom scripts |
885
885
  | **Search** | Built-in multi-engine (`search`) | No | No | No |
886
- | **Plugin Ecosystem** | 69 plugins | Limited | Limited | No |
886
+ | **Plugin Ecosystem** | 70+ plugins | Limited | Limited | No |
887
887
  | **No Code Required** | ✅ CLI commands | ❌ Must write JS/TS | ❌ Must write JS/TS | ❌ Must write code |
888
888
  | **Headless Mode** | ✅ Default | ✅ | ✅ | ✅ |
889
889
  | **Record/Replay** | ✅ Built-in (`record`/`replay`) | ✅ Codegen | ❌ | ❌ |
@@ -20,7 +20,7 @@ import {
20
20
  saveSessionDiskMeta,
21
21
  setActivePage,
22
22
  touchSession
23
- } from "./chunk-RGS6ECTH.js";
23
+ } from "./chunk-M4U63K6B.js";
24
24
  import "./chunk-TNEN6VQ2.js";
25
25
  import "./chunk-GDKLH7ZY.js";
26
26
  import "./chunk-KFQGP6VL.js";
@@ -20,8 +20,8 @@ import {
20
20
  saveSessionDiskMeta,
21
21
  setActivePage,
22
22
  touchSession
23
- } from "./chunk-LYVU6SD3.js";
24
- import "./chunk-2RHJEYWU.js";
23
+ } from "./chunk-TP6RHIWG.js";
24
+ import "./chunk-JGITVXIU.js";
25
25
  import "./chunk-TNEN6VQ2.js";
26
26
  import "./chunk-GDKLH7ZY.js";
27
27
  import "./chunk-ABXMBNQ6.js";
@@ -20,8 +20,8 @@ import {
20
20
  saveSessionDiskMeta,
21
21
  setActivePage,
22
22
  touchSession
23
- } from "./chunk-NQSFETTQ.js";
24
- import "./chunk-2RHJEYWU.js";
23
+ } from "./chunk-J7LDVYWI.js";
24
+ import "./chunk-JGITVXIU.js";
25
25
  import "./chunk-TNEN6VQ2.js";
26
26
  import "./chunk-GDKLH7ZY.js";
27
27
  import "./chunk-KFQGP6VL.js";
@@ -14,7 +14,7 @@ import {
14
14
  scrollIntoView,
15
15
  waitForActionable,
16
16
  waitForNetworkIdle
17
- } from "./chunk-2RHJEYWU.js";
17
+ } from "./chunk-JGITVXIU.js";
18
18
  import {
19
19
  connectToCDP,
20
20
  findChrome,
@@ -303,6 +303,18 @@ function queryAllJS(selector) {
303
303
  async function waitForActionable(page, selector, opts = {}) {
304
304
  const timeout = opts.timeout ?? 3e4;
305
305
  if (opts.force) {
306
+ if (selector.startsWith("xpath=")) {
307
+ const rect2 = await page.evaluate(`
308
+ (function() {
309
+ const el = ${queryJS(selector)};
310
+ if (!el) return null;
311
+ const r = el.getBoundingClientRect();
312
+ return { x: r.x, y: r.y, width: r.width, height: r.height };
313
+ })()
314
+ `);
315
+ if (!rect2) throw new Error(`Element not found: ${selector}`);
316
+ return { nodeId: 0, rect: rect2 };
317
+ }
306
318
  const nodeId = await page.querySelector(selector);
307
319
  if (!nodeId) throw new Error(`Element not found: ${selector}`);
308
320
  const rect = await page.getBoxModel(nodeId);
@@ -1656,6 +1668,22 @@ Last error: ${lastError.message}` : "";
1656
1668
  // ── CDP helpers exposed for locator/element ─────────────────
1657
1669
  /** Query a single element, returns CDP nodeId or 0 if not found */
1658
1670
  async querySelector(selector) {
1671
+ if (selector.startsWith("xpath=")) {
1672
+ const found = await this.evaluate(`
1673
+ (() => { const el = ${queryJS(selector)}; return !!el; })()
1674
+ `).catch(() => false);
1675
+ if (!found) return 0;
1676
+ try {
1677
+ const search = await this.conn.send(
1678
+ "DOM.performSearch",
1679
+ { query: selector.slice(6) },
1680
+ this.sessionId
1681
+ );
1682
+ if (search.nodeId) return search.nodeId;
1683
+ } catch {
1684
+ }
1685
+ return 1;
1686
+ }
1659
1687
  const doc = await this.conn.send(
1660
1688
  "DOM.getDocument",
1661
1689
  { depth: 0 },
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  launch
3
- } from "./chunk-2RHJEYWU.js";
3
+ } from "./chunk-JGITVXIU.js";
4
4
  import {
5
5
  errMsg
6
6
  } from "./chunk-GDKLH7ZY.js";
@@ -302,6 +302,18 @@ function queryAllJS(selector) {
302
302
  async function waitForActionable(page, selector, opts = {}) {
303
303
  const timeout = opts.timeout ?? 3e4;
304
304
  if (opts.force) {
305
+ if (selector.startsWith("xpath=")) {
306
+ const rect2 = await page.evaluate(`
307
+ (function() {
308
+ const el = ${queryJS(selector)};
309
+ if (!el) return null;
310
+ const r = el.getBoundingClientRect();
311
+ return { x: r.x, y: r.y, width: r.width, height: r.height };
312
+ })()
313
+ `);
314
+ if (!rect2) throw new Error(`Element not found: ${selector}`);
315
+ return { nodeId: 0, rect: rect2 };
316
+ }
305
317
  const nodeId = await page.querySelector(selector);
306
318
  if (!nodeId) throw new Error(`Element not found: ${selector}`);
307
319
  const rect = await page.getBoxModel(nodeId);
@@ -1655,6 +1667,22 @@ Last error: ${lastError.message}` : "";
1655
1667
  // ── CDP helpers exposed for locator/element ─────────────────
1656
1668
  /** Query a single element, returns CDP nodeId or 0 if not found */
1657
1669
  async querySelector(selector) {
1670
+ if (selector.startsWith("xpath=")) {
1671
+ const found = await this.evaluate(`
1672
+ (() => { const el = ${queryJS(selector)}; return !!el; })()
1673
+ `).catch(() => false);
1674
+ if (!found) return 0;
1675
+ try {
1676
+ const search = await this.conn.send(
1677
+ "DOM.performSearch",
1678
+ { query: selector.slice(6) },
1679
+ this.sessionId
1680
+ );
1681
+ if (search.nodeId) return search.nodeId;
1682
+ } catch {
1683
+ }
1684
+ return 1;
1685
+ }
1658
1686
  const doc = await this.conn.send(
1659
1687
  "DOM.getDocument",
1660
1688
  { depth: 0 },
@@ -308,6 +308,18 @@ function queryAllJS(selector) {
308
308
  async function waitForActionable(page, selector, opts = {}) {
309
309
  const timeout = opts.timeout ?? 3e4;
310
310
  if (opts.force) {
311
+ if (selector.startsWith("xpath=")) {
312
+ const rect2 = await page.evaluate(`
313
+ (function() {
314
+ const el = ${queryJS(selector)};
315
+ if (!el) return null;
316
+ const r = el.getBoundingClientRect();
317
+ return { x: r.x, y: r.y, width: r.width, height: r.height };
318
+ })()
319
+ `);
320
+ if (!rect2) throw new Error(`Element not found: ${selector}`);
321
+ return { nodeId: 0, rect: rect2 };
322
+ }
311
323
  const nodeId = await page.querySelector(selector);
312
324
  if (!nodeId) throw new Error(`Element not found: ${selector}`);
313
325
  const rect = await page.getBoxModel(nodeId);
@@ -1661,6 +1673,22 @@ Last error: ${lastError.message}` : "";
1661
1673
  // ── CDP helpers exposed for locator/element ─────────────────
1662
1674
  /** Query a single element, returns CDP nodeId or 0 if not found */
1663
1675
  async querySelector(selector) {
1676
+ if (selector.startsWith("xpath=")) {
1677
+ const found = await this.evaluate(`
1678
+ (() => { const el = ${queryJS(selector)}; return !!el; })()
1679
+ `).catch(() => false);
1680
+ if (!found) return 0;
1681
+ try {
1682
+ const search = await this.conn.send(
1683
+ "DOM.performSearch",
1684
+ { query: selector.slice(6) },
1685
+ this.sessionId
1686
+ );
1687
+ if (search.nodeId) return search.nodeId;
1688
+ } catch {
1689
+ }
1690
+ return 1;
1691
+ }
1664
1692
  const doc = await this.conn.send(
1665
1693
  "DOM.getDocument",
1666
1694
  { depth: 0 },
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  launch
3
- } from "./chunk-2RHJEYWU.js";
3
+ } from "./chunk-JGITVXIU.js";
4
4
  import {
5
5
  errMsg
6
6
  } from "./chunk-GDKLH7ZY.js";
package/dist/cli.js CHANGED
@@ -25,7 +25,7 @@ import {
25
25
  resolveLaunchOpts,
26
26
  saveSessionDiskMeta,
27
27
  setActivePage
28
- } from "./chunk-RGS6ECTH.js";
28
+ } from "./chunk-M4U63K6B.js";
29
29
  import "./chunk-TNEN6VQ2.js";
30
30
  import {
31
31
  forwardCommandLog,
@@ -1208,9 +1208,9 @@ var setViewportCommand = registerCommand({
1208
1208
  description: "Set the viewport size and properties",
1209
1209
  scope: "browser",
1210
1210
  parameters: z11.object({
1211
- width: z11.number(),
1212
- height: z11.number(),
1213
- deviceScaleFactor: z11.number().optional(),
1211
+ width: z11.coerce.number(),
1212
+ height: z11.coerce.number(),
1213
+ deviceScaleFactor: z11.coerce.number().optional(),
1214
1214
  isMobile: z11.boolean().optional(),
1215
1215
  hasTouch: z11.boolean().optional()
1216
1216
  }),
@@ -7083,7 +7083,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
7083
7083
  }
7084
7084
  let targetPageOverride = null;
7085
7085
  if (_target && extraOpts?.cdpEndpoint) {
7086
- const { findTargetPage } = await import("./browser-SOML2EWR.js");
7086
+ const { findTargetPage } = await import("./browser-4KKT6V5S.js");
7087
7087
  targetPageOverride = await findTargetPage(extraOpts.cdpEndpoint, _target);
7088
7088
  if (!targetPageOverride) {
7089
7089
  return errorResult(`Target "${_target}" not found. Use 'xbrowser targets --cdp ${extraOpts.cdpEndpoint}' to list available pages.`);
@@ -11859,7 +11859,8 @@ function printChainResult(chainResult) {
11859
11859
  }
11860
11860
  if (step.tips?.length) {
11861
11861
  for (const tip of step.tips) {
11862
- console.log(` \u{1F4A1} ${tip}`);
11862
+ const text = typeof tip === "string" ? tip : tip.message;
11863
+ if (text) console.log(` \u{1F4A1} ${text}`);
11863
11864
  }
11864
11865
  }
11865
11866
  } else {
@@ -12655,7 +12656,8 @@ async function routeCommand(argvIn, stdinCommands) {
12655
12656
  }
12656
12657
  if (step.tips?.length) {
12657
12658
  for (const tip of step.tips) {
12658
- console.log(` \u{1F4A1} ${tip}`);
12659
+ const text = typeof tip === "string" ? tip : tip.message;
12660
+ if (text) console.log(` \u{1F4A1} ${text}`);
12659
12661
  }
12660
12662
  }
12661
12663
  } else {
@@ -13057,7 +13059,7 @@ async function main() {
13057
13059
  const command = process.argv[2];
13058
13060
  const isLongRunning = command === "preview" || command === "serve";
13059
13061
  if (!isLongRunning) {
13060
- const { ensureProcessCanExit } = await import("./browser-SOML2EWR.js");
13062
+ const { ensureProcessCanExit } = await import("./browser-4KKT6V5S.js");
13061
13063
  await ensureProcessCanExit().catch(() => {
13062
13064
  });
13063
13065
  process.exit(exitCode);
@@ -21,8 +21,8 @@ import {
21
21
  resolveLaunchOpts,
22
22
  saveSessionDiskMeta,
23
23
  setActivePage
24
- } from "./chunk-NQSFETTQ.js";
25
- import "./chunk-2RHJEYWU.js";
24
+ } from "./chunk-J7LDVYWI.js";
25
+ import "./chunk-JGITVXIU.js";
26
26
  import "./chunk-TNEN6VQ2.js";
27
27
  import {
28
28
  getPluginLoader
@@ -1169,9 +1169,9 @@ var setViewportCommand = registerCommand({
1169
1169
  description: "Set the viewport size and properties",
1170
1170
  scope: "browser",
1171
1171
  parameters: z11.object({
1172
- width: z11.number(),
1173
- height: z11.number(),
1174
- deviceScaleFactor: z11.number().optional(),
1172
+ width: z11.coerce.number(),
1173
+ height: z11.coerce.number(),
1174
+ deviceScaleFactor: z11.coerce.number().optional(),
1175
1175
  isMobile: z11.boolean().optional(),
1176
1176
  hasTouch: z11.boolean().optional()
1177
1177
  }),
@@ -6614,7 +6614,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
6614
6614
  }
6615
6615
  let targetPageOverride = null;
6616
6616
  if (_target && extraOpts?.cdpEndpoint) {
6617
- const { findTargetPage } = await import("./browser-2OK26HBS.js");
6617
+ const { findTargetPage } = await import("./browser-MMIFKC36.js");
6618
6618
  targetPageOverride = await findTargetPage(extraOpts.cdpEndpoint, _target);
6619
6619
  if (!targetPageOverride) {
6620
6620
  return errorResult(`Target "${_target}" not found. Use 'xbrowser targets --cdp ${extraOpts.cdpEndpoint}' to list available pages.`);
@@ -8330,7 +8330,7 @@ function createRPCHandler() {
8330
8330
  const isNewFormat = Array.isArray(parsed.actions);
8331
8331
  if (isNewFormat) {
8332
8332
  try {
8333
- const { SessionReplayer } = await import("./session-replayer-YIGRIIDD.js");
8333
+ const { SessionReplayer } = await import("./session-replayer-JIH6H7WX.js");
8334
8334
  const replayer = new SessionReplayer({
8335
8335
  page: session.page,
8336
8336
  stepDelay: slowMo * 500,
package/dist/index.js CHANGED
@@ -81,8 +81,8 @@ import {
81
81
  resolveLaunchOpts,
82
82
  saveSessionDiskMeta,
83
83
  setActivePage
84
- } from "./chunk-LYVU6SD3.js";
85
- import "./chunk-2RHJEYWU.js";
84
+ } from "./chunk-TP6RHIWG.js";
85
+ import "./chunk-JGITVXIU.js";
86
86
  import "./chunk-TNEN6VQ2.js";
87
87
  import {
88
88
  errMsg
@@ -1248,9 +1248,9 @@ var setViewportCommand = registerCommand({
1248
1248
  description: "Set the viewport size and properties",
1249
1249
  scope: "browser",
1250
1250
  parameters: z11.object({
1251
- width: z11.number(),
1252
- height: z11.number(),
1253
- deviceScaleFactor: z11.number().optional(),
1251
+ width: z11.coerce.number(),
1252
+ height: z11.coerce.number(),
1253
+ deviceScaleFactor: z11.coerce.number().optional(),
1254
1254
  isMobile: z11.boolean().optional(),
1255
1255
  hasTouch: z11.boolean().optional()
1256
1256
  }),
@@ -7403,7 +7403,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
7403
7403
  }
7404
7404
  let targetPageOverride = null;
7405
7405
  if (_target && extraOpts?.cdpEndpoint) {
7406
- const { findTargetPage } = await import("./browser-ZTTS2SVT.js");
7406
+ const { findTargetPage } = await import("./browser-FKKMMB44.js");
7407
7407
  targetPageOverride = await findTargetPage(extraOpts.cdpEndpoint, _target);
7408
7408
  if (!targetPageOverride) {
7409
7409
  return errorResult(`Target "${_target}" not found. Use 'xbrowser targets --cdp ${extraOpts.cdpEndpoint}' to list available pages.`);
@@ -12199,7 +12199,8 @@ function printChainResult(chainResult) {
12199
12199
  }
12200
12200
  if (step.tips?.length) {
12201
12201
  for (const tip of step.tips) {
12202
- console.log(` \u{1F4A1} ${tip}`);
12202
+ const text = typeof tip === "string" ? tip : tip.message;
12203
+ if (text) console.log(` \u{1F4A1} ${text}`);
12203
12204
  }
12204
12205
  }
12205
12206
  } else {
@@ -12995,7 +12996,8 @@ async function routeCommand(argvIn, stdinCommands) {
12995
12996
  }
12996
12997
  if (step.tips?.length) {
12997
12998
  for (const tip of step.tips) {
12998
- console.log(` \u{1F4A1} ${tip}`);
12999
+ const text = typeof tip === "string" ? tip : tip.message;
13000
+ if (text) console.log(` \u{1F4A1} ${text}`);
12999
13001
  }
13000
13002
  }
13001
13003
  } else {
@@ -16184,7 +16186,7 @@ var DataCollector = class {
16184
16186
  return results;
16185
16187
  }
16186
16188
  async createBrowserContext() {
16187
- const { launch } = await import("./cdp-driver-UGO45HXR.js");
16189
+ const { launch } = await import("./cdp-driver-OY67R2KD.js");
16188
16190
  const { browser } = await launch({
16189
16191
  headless: true,
16190
16192
  args: ["--no-sandbox", "--disable-setuid-sandbox"]
@@ -31,7 +31,7 @@ var SessionReplayer = class {
31
31
  if (this.opts.page) {
32
32
  this.page = this.opts.page;
33
33
  } else if (this.opts.cdpUrl) {
34
- const { launch } = await import("./cdp-driver-UGO45HXR.js");
34
+ const { launch } = await import("./cdp-driver-OY67R2KD.js");
35
35
  const { browser } = await launch({ cdpEndpoint: this.opts.cdpUrl });
36
36
  let contexts = browser.contexts();
37
37
  for (let i = 0; i < 10 && contexts.length === 0; i++) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xbrowser/cli",
3
- "version": "1.4.6",
3
+ "version": "1.4.8",
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": {