@xbrowser/cli 1.0.6 → 1.0.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
@@ -1,6 +1,6 @@
1
1
  # xbrowser
2
2
 
3
- > **Browser automation CLI** for web scraping, headless browsing, SEO analysis, and AI agent workflows. 49 commands, 69 plugins. A command-line alternative to Playwright, Puppeteer, and Selenium — **no code required**.
3
+ > **Browser automation CLI** for web scraping, headless browsing, SEO analysis, and AI agent workflows. 49 commands, 20+ plugins. A command-line alternative to Playwright, Puppeteer, and Selenium — **no code required**.
4
4
 
5
5
  [![CI Status](https://github.com/dyyz1993/xbrowser/workflows/CI/badge.svg)](https://github.com/dyyz1993/xbrowser/actions)
6
6
  [![codecov](https://codecov.io/gh/dyyz1993/xbrowser/branch/master/graph/badge.svg)](https://codecov.io/gh/dyyz1993/xbrowser)
@@ -13,7 +13,7 @@
13
13
  - **命令链** — 用 `&&`、`,`、`+`、`->`、`;` 串联多个命令,一行搞定复杂流程
14
14
  - **管道 & Heredoc** — 支持 stdin 管道和 heredoc 批量执行
15
15
  - **录制 / 回放** — 录制浏览器操作为 YAML,随时回放,可转换为 JS/Python/Bash 脚本
16
- - **插件系统** — 基于 `@dyyz1993/xcli-core`,用 TypeScript 编写站点插件
16
+ - **插件系统** — 基于 `@dyyz1993/xcli-core`,用 TypeScript 编写站点插件。快速开始:`xbrowser create my-plugin --template static`
17
17
  - **CDP 连接** — 连接已运行的 Chrome/Chromium,无需重新启动浏览器
18
18
  - **会话管理** — 多会话并行,独立上下文
19
19
  - **Daemon 模式** — 后台常驻,快速响应
@@ -50,8 +50,10 @@ xbrowser "goto https://example.com && wait .content && text --selector .content"
50
50
 
51
51
  **SEO & Backlink Automation**
52
52
  ```bash
53
- # Auto-publish articles to 13+ SEO platforms
54
- xbrowser publish --platform devto,juejin --file article.md
53
+ # Publish articles to SEO platforms via site plugins
54
+ xbrowser devto publish --file article.md
55
+ xbrowser juejin publish --file article.md
56
+ # (Each platform has its own plugin — run `xbrowser plugin list` to see available)
55
57
  ```
56
58
 
57
59
  ## 快速开始
@@ -256,7 +258,7 @@ xbrowser --cdp auto "goto https://example.com , title"
256
258
  | `html --selector "#main"` | 获取指定元素 HTML | `xbrowser html --selector "#main"` |
257
259
  | `text` | 获取页面文本 | `xbrowser text` |
258
260
  | `text --selector "#article"` | 获取指定元素文本 | `xbrowser text --selector "#article"` |
259
- | `getProperty <selector> <property>` | 获取元素属性 | `xbrowser getProperty "#link" href` |
261
+ | `eval "expression"` | 执行 JS 获取元素属性 | `xbrowser eval "document.querySelector('#link').href"` |
260
262
 
261
263
  ### 截图与快照
262
264
 
@@ -282,20 +284,20 @@ xbrowser --cdp auto "goto https://example.com , title"
282
284
  |------|------|------|
283
285
  | `eval <expression>` | 执行 JS 表达式 | `xbrowser eval "document.title"` |
284
286
  | `eval "1 + 2"` | 计算表达式 | `xbrowser eval "1 + 2"` |
285
- | `evaluateFn <fn> --args 1 2` | 执行带参数的函数 | `xbrowser evaluateFn "return args[0] + args[1]" --args 1 2` |
287
+ | `eval "(a, b) => a + b" --args 1 2` | 执行带参数的函数 | `xbrowser eval "(a, b) => a + b" --args 1 2` |
286
288
 
287
289
  ### 存储
288
290
 
289
291
  | 命令 | 说明 | 示例 |
290
292
  |------|------|------|
291
- | `getCookies` | 获取所有 Cookie | `xbrowser getCookies` |
292
- | `setCookie <name> <value>` | 设置 Cookie | `xbrowser setCookie session abc123` |
293
- | `setCookie <name> <value> --domain .example.com` | 指定域名 | `xbrowser setCookie session abc123 --domain .example.com` |
294
- | `clearCookies` | 清除所有 Cookie | `xbrowser clearCookies` |
295
- | `getLocalStorage` | 获取所有 localStorage | `xbrowser getLocalStorage` |
296
- | `getLocalStorage --key token` | 获取指定 key | `xbrowser getLocalStorage --key token` |
297
- | `setLocalStorage <key> <value>` | 设置 localStorage | `xbrowser setLocalStorage token "abc"` |
298
- | `clearLocalStorage` | 清除 localStorage | `xbrowser clearLocalStorage` |
293
+ | `get-cookies` | 获取所有 Cookie | `xbrowser get-cookies` |
294
+ | `set-cookie <name> <value>` | 设置 Cookie | `xbrowser set-cookie session abc123` |
295
+ | `set-cookie <name> <value> --domain .example.com` | 指定域名 | `xbrowser set-cookie session abc123 --domain .example.com` |
296
+ | `clear-cookies` | 清除所有 Cookie | `xbrowser clear-cookies` |
297
+ | `get-local-storage` | 获取所有 localStorage | `xbrowser get-local-storage` |
298
+ | `get-local-storage --key token` | 获取指定 key | `xbrowser get-local-storage --key token` |
299
+ | `set-local-storage <key> <value>` | 设置 localStorage | `xbrowser set-local-storage token "abc"` |
300
+ | `clear-local-storage` | 清除 localStorage | `xbrowser clear-local-storage` |
299
301
 
300
302
  ### 帧操作
301
303
 
@@ -309,8 +311,8 @@ xbrowser --cdp auto "goto https://example.com , title"
309
311
 
310
312
  | 命令 | 说明 | 示例 |
311
313
  |------|------|------|
312
- | `setViewport <width> <height>` | 设置视口大小 | `xbrowser setViewport 1920 1080` |
313
- | `setViewport 375 812 --isMobile true` | 移动设备模式 | `xbrowser setViewport 375 812 --isMobile true` |
314
+ | `set-viewport <width> <height>` | 设置视口大小 | `xbrowser set-viewport 1920 1080` |
315
+ | `set-viewport 375 812 --isMobile true` | 移动设备模式 | `xbrowser set-viewport 375 812 --isMobile true` |
314
316
 
315
317
  ### 配置管理
316
318
 
@@ -416,7 +418,10 @@ xbrowser create my-plugin --template static --force
416
418
  | `;` | 分割管线(前一个完成后开始下一个) | `goto https://example.com ; goto https://other.com` |
417
419
  | `\|\|` | 前一步成功则跳过后续 | `goto https://example.com \|\| goto https://fallback.com` |
418
420
 
419
- **注意**:命令链中如果包含特殊字符(如 `#`、`>`),需要用引号包裹整个命令链或对单个参数加引号。
421
+ **注意**:
422
+ - 命令链中如果包含特殊字符(如 `#`、`>`),需要用引号包裹整个命令链或对单个参数加引号。
423
+ - `->` 和 `+` 分隔符要求两边有空格(`goto url -> title` ✅,`goto url->title` ❌)。
424
+ - `,` 和 `;` 不要求空格。
420
425
 
421
426
  ```bash
422
427
  # 正确 — 整体引号
@@ -440,7 +445,7 @@ project > browser > page > element
440
445
  | Scope | 说明 | 需要的条件 | 典型命令 |
441
446
  |-------|------|------------|----------|
442
447
  | **project** | 项目级别 | 无 | config, daemon, plugin |
443
- | **browser** | 浏览器级别 | 浏览器实例 | setViewport, session |
448
+ | **browser** | 浏览器级别 | 浏览器实例 | set-viewport, session |
444
449
  | **page** | 页面级别 | 活跃页面 | goto, wait, scroll, screenshot |
445
450
  | **element** | 元素级别 | 页面中的元素 | click, fill, type, hover |
446
451
 
@@ -20,8 +20,8 @@ import {
20
20
  saveSessionDiskMeta,
21
21
  setActivePage,
22
22
  touchSession
23
- } from "./chunk-5QAYN5EZ.js";
24
- import "./chunk-SEFIJY2M.js";
23
+ } from "./chunk-EUHVZVVL.js";
24
+ import "./chunk-IX4JY6OO.js";
25
25
  import "./chunk-TNEN6VQ2.js";
26
26
  import "./chunk-GDKLH7ZY.js";
27
27
  import "./chunk-ABXMBNQ6.js";
@@ -20,7 +20,7 @@ import {
20
20
  saveSessionDiskMeta,
21
21
  setActivePage,
22
22
  touchSession
23
- } from "./chunk-7POCCXIB.js";
23
+ } from "./chunk-HRTXMFW4.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-MXG2H3HJ.js";
24
- import "./chunk-SEFIJY2M.js";
23
+ } from "./chunk-JUNEBEGF.js";
24
+ import "./chunk-IX4JY6OO.js";
25
25
  import "./chunk-TNEN6VQ2.js";
26
26
  import "./chunk-GDKLH7ZY.js";
27
27
  import "./chunk-KFQGP6VL.js";
@@ -1669,6 +1669,10 @@ Last error: ${lastError.message}` : "";
1669
1669
  }
1670
1670
  };
1671
1671
  this._emit("dialog", dialog);
1672
+ setTimeout(() => {
1673
+ this.conn.send("Page.handleJavaScriptDialog", { accept: false }, this.sessionId).catch(() => {
1674
+ });
1675
+ }, 100);
1672
1676
  })
1673
1677
  );
1674
1678
  this._subscriptions.push(
@@ -14,7 +14,7 @@ import {
14
14
  scrollIntoView,
15
15
  waitForActionable,
16
16
  waitForNetworkIdle
17
- } from "./chunk-SEFIJY2M.js";
17
+ } from "./chunk-IX4JY6OO.js";
18
18
  import {
19
19
  connectToCDP,
20
20
  findChrome,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  launch
3
- } from "./chunk-SEFIJY2M.js";
3
+ } from "./chunk-IX4JY6OO.js";
4
4
  import {
5
5
  errMsg
6
6
  } from "./chunk-GDKLH7ZY.js";
@@ -1674,6 +1674,10 @@ Last error: ${lastError.message}` : "";
1674
1674
  }
1675
1675
  };
1676
1676
  this._emit("dialog", dialog);
1677
+ setTimeout(() => {
1678
+ this.conn.send("Page.handleJavaScriptDialog", { accept: false }, this.sessionId).catch(() => {
1679
+ });
1680
+ }, 100);
1677
1681
  })
1678
1682
  );
1679
1683
  this._subscriptions.push(
@@ -1668,6 +1668,10 @@ Last error: ${lastError.message}` : "";
1668
1668
  }
1669
1669
  };
1670
1670
  this._emit("dialog", dialog);
1671
+ setTimeout(() => {
1672
+ this.conn.send("Page.handleJavaScriptDialog", { accept: false }, this.sessionId).catch(() => {
1673
+ });
1674
+ }, 100);
1671
1675
  })
1672
1676
  );
1673
1677
  this._subscriptions.push(
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  launch
3
- } from "./chunk-SEFIJY2M.js";
3
+ } from "./chunk-IX4JY6OO.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-7POCCXIB.js";
28
+ } from "./chunk-HRTXMFW4.js";
29
29
  import "./chunk-TNEN6VQ2.js";
30
30
  import {
31
31
  forwardCommandLog,
@@ -2131,6 +2131,7 @@ function createTurndown() {
2131
2131
  return turndown;
2132
2132
  }
2133
2133
  function postClean(md) {
2134
+ md = md.replace(/<(?:table|div|tbody|thead|tr|td|th|span|colgroup|col)\b[^>]*(?:>[\s\S]{200,}?<\/(?:table|div|tbody|thead|tr|td|th|span|colgroup|col)>)/g, "\n[\u26A0\uFE0F HTML block removed \u2014 complex table/layout not converted to Markdown]\n");
2134
2135
  md = md.replace(/\n{3,}/g, "\n\n");
2135
2136
  md = md.replace(/!\[[^\]]*\]\(\s*\)/g, "");
2136
2137
  md = md.replace(/\[([^\]]*)\]\(\s*\)/g, "$1");
@@ -6966,7 +6967,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
6966
6967
  }
6967
6968
  let targetPageOverride = null;
6968
6969
  if (_target && extraOpts?.cdpEndpoint) {
6969
- const { findTargetPage } = await import("./browser-U4VWPTS2.js");
6970
+ const { findTargetPage } = await import("./browser-AN6MKGOD.js");
6970
6971
  targetPageOverride = await findTargetPage(extraOpts.cdpEndpoint, _target);
6971
6972
  if (!targetPageOverride) {
6972
6973
  return errorResult(`Target "${_target}" not found. Use 'xbrowser targets --cdp ${extraOpts.cdpEndpoint}' to list available pages.`);
@@ -8810,6 +8811,8 @@ var TEMPLATES = {
8810
8811
  {
8811
8812
  path: "index.ts",
8812
8813
  content: [
8814
+ `import { z } from 'zod';`,
8815
+ `import { ok } from '@dyyz1993/xcli-core';`,
8813
8816
  `import type { XCLIAPI } from '@dyyz1993/xcli-core';`,
8814
8817
  ``,
8815
8818
  `export default function (xcli: XCLIAPI): void {`,
@@ -8827,7 +8830,7 @@ var TEMPLATES = {
8827
8830
  ` (sel: string) => document.querySelector(sel)?.textContent ?? '',`,
8828
8831
  ` params.selector || 'body'`,
8829
8832
  ` );`,
8830
- ` return { ok: true, text };`,
8833
+ ` return ok({ text });`,
8831
8834
  ` },`,
8832
8835
  ` });`,
8833
8836
  `}`
@@ -8835,14 +8838,21 @@ var TEMPLATES = {
8835
8838
  },
8836
8839
  {
8837
8840
  path: "package.json",
8838
- content: `{
8839
- "name": "{{projectName}}",
8840
- "version": "1.0.0",
8841
- "dependencies": {
8842
- "zod": "^3.24.0"
8843
- }
8844
- }
8845
- `
8841
+ content: [
8842
+ `{`,
8843
+ ` "name": "xbrowser-plugin-{{projectName}}",`,
8844
+ ` "version": "1.0.0",`,
8845
+ ` "main": "index.ts",`,
8846
+ ` "type": "module",`,
8847
+ ` "dependencies": { "zod": "^3.24.0" },`,
8848
+ ` "peerDependencies": { "@dyyz1993/xcli-core": ">=1.0.0" },`,
8849
+ ` "xbrowser": {`,
8850
+ ` "name": "{{projectName}}",`,
8851
+ ` "description": "A static page plugin",`,
8852
+ ` "commands": ["scrape"]`,
8853
+ ` }`,
8854
+ `}`
8855
+ ].join("\n")
8846
8856
  }
8847
8857
  ]
8848
8858
  },
@@ -8853,6 +8863,8 @@ var TEMPLATES = {
8853
8863
  {
8854
8864
  path: "index.ts",
8855
8865
  content: [
8866
+ `import { z } from 'zod';`,
8867
+ `import { ok } from '@dyyz1993/xcli-core';`,
8856
8868
  `import type { XCLIAPI } from '@dyyz1993/xcli-core';`,
8857
8869
  ``,
8858
8870
  `export default function (xcli: XCLIAPI): void {`,
@@ -8867,7 +8879,7 @@ var TEMPLATES = {
8867
8879
  ` parameters: z.object({ url: z.string() }),`,
8868
8880
  ` handler: async (params, ctx) => {`,
8869
8881
  ` await ctx.page.goto(params.url);`,
8870
- ` return { ok: true };`,
8882
+ ` return ok({ url: params.url });`,
8871
8883
  ` },`,
8872
8884
  ` });`,
8873
8885
  ``,
@@ -8882,7 +8894,7 @@ var TEMPLATES = {
8882
8894
  ` case 'fill': await page.fill(params.selector, ''); break;`,
8883
8895
  ` case 'hover': await page.hover(params.selector); break;`,
8884
8896
  ` }`,
8885
- ` return { ok: true };`,
8897
+ ` return ok({ action: params.action, selector: params.selector });`,
8886
8898
  ` },`,
8887
8899
  ` });`,
8888
8900
  `}`
@@ -8890,14 +8902,21 @@ var TEMPLATES = {
8890
8902
  },
8891
8903
  {
8892
8904
  path: "package.json",
8893
- content: `{
8894
- "name": "{{projectName}}",
8895
- "version": "1.0.0",
8896
- "dependencies": {
8897
- "zod": "^3.24.0"
8898
- }
8899
- }
8900
- `
8905
+ content: [
8906
+ `{`,
8907
+ ` "name": "xbrowser-plugin-{{projectName}}",`,
8908
+ ` "version": "1.0.0",`,
8909
+ ` "main": "index.ts",`,
8910
+ ` "type": "module",`,
8911
+ ` "dependencies": { "zod": "^3.24.0" },`,
8912
+ ` "peerDependencies": { "@dyyz1993/xcli-core": ">=1.0.0" },`,
8913
+ ` "xbrowser": {`,
8914
+ ` "name": "{{projectName}}",`,
8915
+ ` "description": "A dynamic page plugin",`,
8916
+ ` "commands": ["navigate", "interact"]`,
8917
+ ` }`,
8918
+ `}`
8919
+ ].join("\n")
8901
8920
  }
8902
8921
  ]
8903
8922
  },
@@ -8908,6 +8927,8 @@ var TEMPLATES = {
8908
8927
  {
8909
8928
  path: "index.ts",
8910
8929
  content: [
8930
+ `import { z } from 'zod';`,
8931
+ `import { ok } from '@dyyz1993/xcli-core';`,
8911
8932
  `import type { XCLIAPI } from '@dyyz1993/xcli-core';`,
8912
8933
  ``,
8913
8934
  `export default function (xcli: XCLIAPI): void {`,
@@ -8921,14 +8942,13 @@ var TEMPLATES = {
8921
8942
  ` description: 'Check login status',`,
8922
8943
  ` scope: 'project',`,
8923
8944
  ` parameters: z.object({}),`,
8924
- ` handler: async (_params, ctx) => {`,
8945
+ ` handler: async () => {`,
8925
8946
  ` const loggedIn = await site.isLoggedIn();`,
8926
- ` return { ok: true, loggedIn };`,
8947
+ ` return ok({ loggedIn });`,
8927
8948
  ` },`,
8928
8949
  ` });`,
8929
8950
  ``,
8930
8951
  ` site.login(async (ctx) => {`,
8931
- ` console.log('Login handler for {{projectName}}');`,
8932
8952
  ` await ctx.storage.set('auth_token', 'dummy');`,
8933
8953
  ` });`,
8934
8954
  ``,
@@ -8940,14 +8960,22 @@ var TEMPLATES = {
8940
8960
  },
8941
8961
  {
8942
8962
  path: "package.json",
8943
- content: `{
8944
- "name": "{{projectName}}",
8945
- "version": "1.0.0",
8946
- "dependencies": {
8947
- "zod": "^3.24.0"
8948
- }
8949
- }
8950
- `
8963
+ content: [
8964
+ `{`,
8965
+ ` "name": "xbrowser-plugin-{{projectName}}",`,
8966
+ ` "version": "1.0.0",`,
8967
+ ` "main": "index.ts",`,
8968
+ ` "type": "module",`,
8969
+ ` "dependencies": { "zod": "^3.24.0" },`,
8970
+ ` "peerDependencies": { "@dyyz1993/xcli-core": ">=1.0.0" },`,
8971
+ ` "xbrowser": {`,
8972
+ ` "name": "{{projectName}}",`,
8973
+ ` "description": "A plugin with login/logout support",`,
8974
+ ` "requiresLogin": true,`,
8975
+ ` "commands": ["check"]`,
8976
+ ` }`,
8977
+ `}`
8978
+ ].join("\n")
8951
8979
  }
8952
8980
  ]
8953
8981
  },
@@ -8958,6 +8986,8 @@ var TEMPLATES = {
8958
8986
  {
8959
8987
  path: "index.ts",
8960
8988
  content: [
8989
+ `import { z } from 'zod';`,
8990
+ `import { ok } from '@dyyz1993/xcli-core';`,
8961
8991
  `import type { XCLIAPI } from '@dyyz1993/xcli-core';`,
8962
8992
  ``,
8963
8993
  `export default function (xcli: XCLIAPI): void {`,
@@ -8970,9 +9000,9 @@ var TEMPLATES = {
8970
9000
  ` description: 'Fetch data from API',`,
8971
9001
  ` scope: 'project',`,
8972
9002
  ` parameters: z.object({ endpoint: z.string(), method: z.enum(['GET', 'POST']).optional() }),`,
8973
- ` handler: async (params, _ctx) => {`,
9003
+ ` handler: async (params) => {`,
8974
9004
  ` const method = params.method || 'GET';`,
8975
- ` return { ok: true, endpoint: params.endpoint, method };`,
9005
+ ` return ok({ endpoint: params.endpoint, method });`,
8976
9006
  ` },`,
8977
9007
  ` });`,
8978
9008
  ``,
@@ -8980,8 +9010,8 @@ var TEMPLATES = {
8980
9010
  ` description: 'List available endpoints',`,
8981
9011
  ` scope: 'project',`,
8982
9012
  ` parameters: z.object({}),`,
8983
- ` handler: async (_params, _ctx) => {`,
8984
- ` return { ok: true, endpoints: ['/api/data', '/api/status'] };`,
9013
+ ` handler: async () => {`,
9014
+ ` return ok({ endpoints: ['/api/data', '/api/status'] });`,
8985
9015
  ` },`,
8986
9016
  ` });`,
8987
9017
  `}`
@@ -8989,14 +9019,21 @@ var TEMPLATES = {
8989
9019
  },
8990
9020
  {
8991
9021
  path: "package.json",
8992
- content: `{
8993
- "name": "{{projectName}}",
8994
- "version": "1.0.0",
8995
- "dependencies": {
8996
- "zod": "^3.24.0"
8997
- }
8998
- }
8999
- `
9022
+ content: [
9023
+ `{`,
9024
+ ` "name": "xbrowser-plugin-{{projectName}}",`,
9025
+ ` "version": "1.0.0",`,
9026
+ ` "main": "index.ts",`,
9027
+ ` "type": "module",`,
9028
+ ` "dependencies": { "zod": "^3.24.0" },`,
9029
+ ` "peerDependencies": { "@dyyz1993/xcli-core": ">=1.0.0" },`,
9030
+ ` "xbrowser": {`,
9031
+ ` "name": "{{projectName}}",`,
9032
+ ` "description": "An API integration plugin",`,
9033
+ ` "commands": ["fetch", "list-endpoints"]`,
9034
+ ` }`,
9035
+ `}`
9036
+ ].join("\n")
9000
9037
  }
9001
9038
  ]
9002
9039
  }
@@ -9611,6 +9648,18 @@ async function handleBrowserCommand(command, args, options, sessionName, mode, c
9611
9648
  params = { width, height };
9612
9649
  break;
9613
9650
  }
9651
+ case "mouse": {
9652
+ const action = options.action || args.find((a) => ["move", "click", "dblclick", "down", "up"].includes(a));
9653
+ const actionIdx = action ? args.indexOf(action) : -1;
9654
+ const x = options.x !== void 0 ? Number(options.x) : actionIdx >= 0 && args[actionIdx + 1] ? Number(args[actionIdx + 1]) : void 0;
9655
+ const y = options.y !== void 0 ? Number(options.y) : actionIdx >= 0 && args[actionIdx + 2] ? Number(args[actionIdx + 2]) : void 0;
9656
+ if (!action || x === void 0 || y === void 0) {
9657
+ outputError("Usage: xbrowser mouse <move|click|dblclick> <x> <y>\n xbrowser mouse --action <action> --x <x> --y <y>");
9658
+ }
9659
+ cmdName = "mouse";
9660
+ params = { action, x, y, ...options.button ? { button: options.button } : {} };
9661
+ break;
9662
+ }
9614
9663
  case "html":
9615
9664
  cmdName = "html";
9616
9665
  params = { selector: options.selector || options.s };
@@ -10147,6 +10196,11 @@ async function handlePlugin(args, options, mode) {
10147
10196
  case "uninstall": {
10148
10197
  const name = subArgs[0];
10149
10198
  if (!name) outputError("Usage: xbrowser plugin uninstall <name>");
10199
+ const installed = await installer.list();
10200
+ const exists = installed.some((p) => p.name === name || p.metadata?.name === name);
10201
+ if (!exists) {
10202
+ outputError(`Plugin "${name}" is not installed. Use 'xbrowser plugin list' to see installed plugins.`);
10203
+ }
10150
10204
  await installer.uninstall(name);
10151
10205
  outputResult({ ok: true, name }, mode);
10152
10206
  break;
@@ -12553,7 +12607,7 @@ async function handleServe(_args, options, mode) {
12553
12607
  \u{1F310} xbrowser HTTP Server`);
12554
12608
  console.log(`
12555
12609
  URL: ${output.url}`);
12556
- console.log(` Auth: ${output.authRequired ? "Enabled (Bearer token)" : "Disabled (dev mode)"}
12610
+ console.log(` Auth: ${output.authRequired ? "Enabled (Bearer token, except /health)" : "Disabled (dev mode)"}
12557
12611
  `);
12558
12612
  console.log(` Endpoints:`);
12559
12613
  for (const [, value] of Object.entries(output.endpoints)) {
@@ -12673,7 +12727,7 @@ async function main() {
12673
12727
  const command = process.argv[2];
12674
12728
  const isLongRunning = command === "preview" || command === "serve";
12675
12729
  if (!isLongRunning) {
12676
- const { ensureProcessCanExit } = await import("./browser-U4VWPTS2.js");
12730
+ const { ensureProcessCanExit } = await import("./browser-AN6MKGOD.js");
12677
12731
  await ensureProcessCanExit().catch(() => {
12678
12732
  });
12679
12733
  process.exit(exitCode);
@@ -21,8 +21,8 @@ import {
21
21
  resolveLaunchOpts,
22
22
  saveSessionDiskMeta,
23
23
  setActivePage
24
- } from "./chunk-MXG2H3HJ.js";
25
- import "./chunk-SEFIJY2M.js";
24
+ } from "./chunk-JUNEBEGF.js";
25
+ import "./chunk-IX4JY6OO.js";
26
26
  import "./chunk-TNEN6VQ2.js";
27
27
  import {
28
28
  getDaemonConfig,
@@ -2089,6 +2089,7 @@ function createTurndown() {
2089
2089
  return turndown;
2090
2090
  }
2091
2091
  function postClean(md) {
2092
+ md = md.replace(/<(?:table|div|tbody|thead|tr|td|th|span|colgroup|col)\b[^>]*(?:>[\s\S]{200,}?<\/(?:table|div|tbody|thead|tr|td|th|span|colgroup|col)>)/g, "\n[\u26A0\uFE0F HTML block removed \u2014 complex table/layout not converted to Markdown]\n");
2092
2093
  md = md.replace(/\n{3,}/g, "\n\n");
2093
2094
  md = md.replace(/!\[[^\]]*\]\(\s*\)/g, "");
2094
2095
  md = md.replace(/\[([^\]]*)\]\(\s*\)/g, "$1");
@@ -6924,7 +6925,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
6924
6925
  }
6925
6926
  let targetPageOverride = null;
6926
6927
  if (_target && extraOpts?.cdpEndpoint) {
6927
- const { findTargetPage } = await import("./browser-AXCKBSWS.js");
6928
+ const { findTargetPage } = await import("./browser-MUPES4UY.js");
6928
6929
  targetPageOverride = await findTargetPage(extraOpts.cdpEndpoint, _target);
6929
6930
  if (!targetPageOverride) {
6930
6931
  return errorResult(`Target "${_target}" not found. Use 'xbrowser targets --cdp ${extraOpts.cdpEndpoint}' to list available pages.`);
@@ -8608,7 +8609,7 @@ function createRPCHandler() {
8608
8609
  const isNewFormat = Array.isArray(parsed.actions);
8609
8610
  if (isNewFormat) {
8610
8611
  try {
8611
- const { SessionReplayer } = await import("./session-replayer-GCGY6KFK.js");
8612
+ const { SessionReplayer } = await import("./session-replayer-YWMSSZWC.js");
8612
8613
  const replayer = new SessionReplayer({
8613
8614
  page: session.page,
8614
8615
  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-5QAYN5EZ.js";
85
- import "./chunk-SEFIJY2M.js";
84
+ } from "./chunk-EUHVZVVL.js";
85
+ import "./chunk-IX4JY6OO.js";
86
86
  import "./chunk-TNEN6VQ2.js";
87
87
  import {
88
88
  errMsg
@@ -2171,6 +2171,7 @@ function createTurndown() {
2171
2171
  return turndown;
2172
2172
  }
2173
2173
  function postClean(md) {
2174
+ md = md.replace(/<(?:table|div|tbody|thead|tr|td|th|span|colgroup|col)\b[^>]*(?:>[\s\S]{200,}?<\/(?:table|div|tbody|thead|tr|td|th|span|colgroup|col)>)/g, "\n[\u26A0\uFE0F HTML block removed \u2014 complex table/layout not converted to Markdown]\n");
2174
2175
  md = md.replace(/\n{3,}/g, "\n\n");
2175
2176
  md = md.replace(/!\[[^\]]*\]\(\s*\)/g, "");
2176
2177
  md = md.replace(/\[([^\]]*)\]\(\s*\)/g, "$1");
@@ -7286,7 +7287,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
7286
7287
  }
7287
7288
  let targetPageOverride = null;
7288
7289
  if (_target && extraOpts?.cdpEndpoint) {
7289
- const { findTargetPage } = await import("./browser-X7OVRKJH.js");
7290
+ const { findTargetPage } = await import("./browser-6QN42A4K.js");
7290
7291
  targetPageOverride = await findTargetPage(extraOpts.cdpEndpoint, _target);
7291
7292
  if (!targetPageOverride) {
7292
7293
  return errorResult(`Target "${_target}" not found. Use 'xbrowser targets --cdp ${extraOpts.cdpEndpoint}' to list available pages.`);
@@ -9145,6 +9146,8 @@ var TEMPLATES = {
9145
9146
  {
9146
9147
  path: "index.ts",
9147
9148
  content: [
9149
+ `import { z } from 'zod';`,
9150
+ `import { ok } from '@dyyz1993/xcli-core';`,
9148
9151
  `import type { XCLIAPI } from '@dyyz1993/xcli-core';`,
9149
9152
  ``,
9150
9153
  `export default function (xcli: XCLIAPI): void {`,
@@ -9162,7 +9165,7 @@ var TEMPLATES = {
9162
9165
  ` (sel: string) => document.querySelector(sel)?.textContent ?? '',`,
9163
9166
  ` params.selector || 'body'`,
9164
9167
  ` );`,
9165
- ` return { ok: true, text };`,
9168
+ ` return ok({ text });`,
9166
9169
  ` },`,
9167
9170
  ` });`,
9168
9171
  `}`
@@ -9170,14 +9173,21 @@ var TEMPLATES = {
9170
9173
  },
9171
9174
  {
9172
9175
  path: "package.json",
9173
- content: `{
9174
- "name": "{{projectName}}",
9175
- "version": "1.0.0",
9176
- "dependencies": {
9177
- "zod": "^3.24.0"
9178
- }
9179
- }
9180
- `
9176
+ content: [
9177
+ `{`,
9178
+ ` "name": "xbrowser-plugin-{{projectName}}",`,
9179
+ ` "version": "1.0.0",`,
9180
+ ` "main": "index.ts",`,
9181
+ ` "type": "module",`,
9182
+ ` "dependencies": { "zod": "^3.24.0" },`,
9183
+ ` "peerDependencies": { "@dyyz1993/xcli-core": ">=1.0.0" },`,
9184
+ ` "xbrowser": {`,
9185
+ ` "name": "{{projectName}}",`,
9186
+ ` "description": "A static page plugin",`,
9187
+ ` "commands": ["scrape"]`,
9188
+ ` }`,
9189
+ `}`
9190
+ ].join("\n")
9181
9191
  }
9182
9192
  ]
9183
9193
  },
@@ -9188,6 +9198,8 @@ var TEMPLATES = {
9188
9198
  {
9189
9199
  path: "index.ts",
9190
9200
  content: [
9201
+ `import { z } from 'zod';`,
9202
+ `import { ok } from '@dyyz1993/xcli-core';`,
9191
9203
  `import type { XCLIAPI } from '@dyyz1993/xcli-core';`,
9192
9204
  ``,
9193
9205
  `export default function (xcli: XCLIAPI): void {`,
@@ -9202,7 +9214,7 @@ var TEMPLATES = {
9202
9214
  ` parameters: z.object({ url: z.string() }),`,
9203
9215
  ` handler: async (params, ctx) => {`,
9204
9216
  ` await ctx.page.goto(params.url);`,
9205
- ` return { ok: true };`,
9217
+ ` return ok({ url: params.url });`,
9206
9218
  ` },`,
9207
9219
  ` });`,
9208
9220
  ``,
@@ -9217,7 +9229,7 @@ var TEMPLATES = {
9217
9229
  ` case 'fill': await page.fill(params.selector, ''); break;`,
9218
9230
  ` case 'hover': await page.hover(params.selector); break;`,
9219
9231
  ` }`,
9220
- ` return { ok: true };`,
9232
+ ` return ok({ action: params.action, selector: params.selector });`,
9221
9233
  ` },`,
9222
9234
  ` });`,
9223
9235
  `}`
@@ -9225,14 +9237,21 @@ var TEMPLATES = {
9225
9237
  },
9226
9238
  {
9227
9239
  path: "package.json",
9228
- content: `{
9229
- "name": "{{projectName}}",
9230
- "version": "1.0.0",
9231
- "dependencies": {
9232
- "zod": "^3.24.0"
9233
- }
9234
- }
9235
- `
9240
+ content: [
9241
+ `{`,
9242
+ ` "name": "xbrowser-plugin-{{projectName}}",`,
9243
+ ` "version": "1.0.0",`,
9244
+ ` "main": "index.ts",`,
9245
+ ` "type": "module",`,
9246
+ ` "dependencies": { "zod": "^3.24.0" },`,
9247
+ ` "peerDependencies": { "@dyyz1993/xcli-core": ">=1.0.0" },`,
9248
+ ` "xbrowser": {`,
9249
+ ` "name": "{{projectName}}",`,
9250
+ ` "description": "A dynamic page plugin",`,
9251
+ ` "commands": ["navigate", "interact"]`,
9252
+ ` }`,
9253
+ `}`
9254
+ ].join("\n")
9236
9255
  }
9237
9256
  ]
9238
9257
  },
@@ -9243,6 +9262,8 @@ var TEMPLATES = {
9243
9262
  {
9244
9263
  path: "index.ts",
9245
9264
  content: [
9265
+ `import { z } from 'zod';`,
9266
+ `import { ok } from '@dyyz1993/xcli-core';`,
9246
9267
  `import type { XCLIAPI } from '@dyyz1993/xcli-core';`,
9247
9268
  ``,
9248
9269
  `export default function (xcli: XCLIAPI): void {`,
@@ -9256,14 +9277,13 @@ var TEMPLATES = {
9256
9277
  ` description: 'Check login status',`,
9257
9278
  ` scope: 'project',`,
9258
9279
  ` parameters: z.object({}),`,
9259
- ` handler: async (_params, ctx) => {`,
9280
+ ` handler: async () => {`,
9260
9281
  ` const loggedIn = await site.isLoggedIn();`,
9261
- ` return { ok: true, loggedIn };`,
9282
+ ` return ok({ loggedIn });`,
9262
9283
  ` },`,
9263
9284
  ` });`,
9264
9285
  ``,
9265
9286
  ` site.login(async (ctx) => {`,
9266
- ` console.log('Login handler for {{projectName}}');`,
9267
9287
  ` await ctx.storage.set('auth_token', 'dummy');`,
9268
9288
  ` });`,
9269
9289
  ``,
@@ -9275,14 +9295,22 @@ var TEMPLATES = {
9275
9295
  },
9276
9296
  {
9277
9297
  path: "package.json",
9278
- content: `{
9279
- "name": "{{projectName}}",
9280
- "version": "1.0.0",
9281
- "dependencies": {
9282
- "zod": "^3.24.0"
9283
- }
9284
- }
9285
- `
9298
+ content: [
9299
+ `{`,
9300
+ ` "name": "xbrowser-plugin-{{projectName}}",`,
9301
+ ` "version": "1.0.0",`,
9302
+ ` "main": "index.ts",`,
9303
+ ` "type": "module",`,
9304
+ ` "dependencies": { "zod": "^3.24.0" },`,
9305
+ ` "peerDependencies": { "@dyyz1993/xcli-core": ">=1.0.0" },`,
9306
+ ` "xbrowser": {`,
9307
+ ` "name": "{{projectName}}",`,
9308
+ ` "description": "A plugin with login/logout support",`,
9309
+ ` "requiresLogin": true,`,
9310
+ ` "commands": ["check"]`,
9311
+ ` }`,
9312
+ `}`
9313
+ ].join("\n")
9286
9314
  }
9287
9315
  ]
9288
9316
  },
@@ -9293,6 +9321,8 @@ var TEMPLATES = {
9293
9321
  {
9294
9322
  path: "index.ts",
9295
9323
  content: [
9324
+ `import { z } from 'zod';`,
9325
+ `import { ok } from '@dyyz1993/xcli-core';`,
9296
9326
  `import type { XCLIAPI } from '@dyyz1993/xcli-core';`,
9297
9327
  ``,
9298
9328
  `export default function (xcli: XCLIAPI): void {`,
@@ -9305,9 +9335,9 @@ var TEMPLATES = {
9305
9335
  ` description: 'Fetch data from API',`,
9306
9336
  ` scope: 'project',`,
9307
9337
  ` parameters: z.object({ endpoint: z.string(), method: z.enum(['GET', 'POST']).optional() }),`,
9308
- ` handler: async (params, _ctx) => {`,
9338
+ ` handler: async (params) => {`,
9309
9339
  ` const method = params.method || 'GET';`,
9310
- ` return { ok: true, endpoint: params.endpoint, method };`,
9340
+ ` return ok({ endpoint: params.endpoint, method });`,
9311
9341
  ` },`,
9312
9342
  ` });`,
9313
9343
  ``,
@@ -9315,8 +9345,8 @@ var TEMPLATES = {
9315
9345
  ` description: 'List available endpoints',`,
9316
9346
  ` scope: 'project',`,
9317
9347
  ` parameters: z.object({}),`,
9318
- ` handler: async (_params, _ctx) => {`,
9319
- ` return { ok: true, endpoints: ['/api/data', '/api/status'] };`,
9348
+ ` handler: async () => {`,
9349
+ ` return ok({ endpoints: ['/api/data', '/api/status'] });`,
9320
9350
  ` },`,
9321
9351
  ` });`,
9322
9352
  `}`
@@ -9324,14 +9354,21 @@ var TEMPLATES = {
9324
9354
  },
9325
9355
  {
9326
9356
  path: "package.json",
9327
- content: `{
9328
- "name": "{{projectName}}",
9329
- "version": "1.0.0",
9330
- "dependencies": {
9331
- "zod": "^3.24.0"
9332
- }
9333
- }
9334
- `
9357
+ content: [
9358
+ `{`,
9359
+ ` "name": "xbrowser-plugin-{{projectName}}",`,
9360
+ ` "version": "1.0.0",`,
9361
+ ` "main": "index.ts",`,
9362
+ ` "type": "module",`,
9363
+ ` "dependencies": { "zod": "^3.24.0" },`,
9364
+ ` "peerDependencies": { "@dyyz1993/xcli-core": ">=1.0.0" },`,
9365
+ ` "xbrowser": {`,
9366
+ ` "name": "{{projectName}}",`,
9367
+ ` "description": "An API integration plugin",`,
9368
+ ` "commands": ["fetch", "list-endpoints"]`,
9369
+ ` }`,
9370
+ `}`
9371
+ ].join("\n")
9335
9372
  }
9336
9373
  ]
9337
9374
  }
@@ -9951,6 +9988,18 @@ async function handleBrowserCommand(command, args, options, sessionName, mode, c
9951
9988
  params = { width, height };
9952
9989
  break;
9953
9990
  }
9991
+ case "mouse": {
9992
+ const action = options.action || args.find((a) => ["move", "click", "dblclick", "down", "up"].includes(a));
9993
+ const actionIdx = action ? args.indexOf(action) : -1;
9994
+ const x = options.x !== void 0 ? Number(options.x) : actionIdx >= 0 && args[actionIdx + 1] ? Number(args[actionIdx + 1]) : void 0;
9995
+ const y = options.y !== void 0 ? Number(options.y) : actionIdx >= 0 && args[actionIdx + 2] ? Number(args[actionIdx + 2]) : void 0;
9996
+ if (!action || x === void 0 || y === void 0) {
9997
+ outputError("Usage: xbrowser mouse <move|click|dblclick> <x> <y>\n xbrowser mouse --action <action> --x <x> --y <y>");
9998
+ }
9999
+ cmdName = "mouse";
10000
+ params = { action, x, y, ...options.button ? { button: options.button } : {} };
10001
+ break;
10002
+ }
9954
10003
  case "html":
9955
10004
  cmdName = "html";
9956
10005
  params = { selector: options.selector || options.s };
@@ -10487,6 +10536,11 @@ async function handlePlugin(args, options, mode) {
10487
10536
  case "uninstall": {
10488
10537
  const name = subArgs[0];
10489
10538
  if (!name) outputError("Usage: xbrowser plugin uninstall <name>");
10539
+ const installed = await installer.list();
10540
+ const exists = installed.some((p) => p.name === name || p.metadata?.name === name);
10541
+ if (!exists) {
10542
+ outputError(`Plugin "${name}" is not installed. Use 'xbrowser plugin list' to see installed plugins.`);
10543
+ }
10490
10544
  await installer.uninstall(name);
10491
10545
  outputResult({ ok: true, name }, mode);
10492
10546
  break;
@@ -12893,7 +12947,7 @@ async function handleServe(_args, options, mode) {
12893
12947
  \u{1F310} xbrowser HTTP Server`);
12894
12948
  console.log(`
12895
12949
  URL: ${output.url}`);
12896
- console.log(` Auth: ${output.authRequired ? "Enabled (Bearer token)" : "Disabled (dev mode)"}
12950
+ console.log(` Auth: ${output.authRequired ? "Enabled (Bearer token, except /health)" : "Disabled (dev mode)"}
12897
12951
  `);
12898
12952
  console.log(` Endpoints:`);
12899
12953
  for (const [, value] of Object.entries(output.endpoints)) {
@@ -15800,7 +15854,7 @@ var DataCollector = class {
15800
15854
  return results;
15801
15855
  }
15802
15856
  async createBrowserContext() {
15803
- const { launch } = await import("./cdp-driver-ZAVN7GRB.js");
15857
+ const { launch } = await import("./cdp-driver-LKNM6OQI.js");
15804
15858
  const { browser } = await launch({
15805
15859
  headless: true,
15806
15860
  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-ZAVN7GRB.js");
34
+ const { launch } = await import("./cdp-driver-LKNM6OQI.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.0.6",
3
+ "version": "1.0.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": {