@xbrowser/cli 1.1.1 → 1.2.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/index.js CHANGED
@@ -81,8 +81,8 @@ import {
81
81
  resolveLaunchOpts,
82
82
  saveSessionDiskMeta,
83
83
  setActivePage
84
- } from "./chunk-MWBVZWXA.js";
85
- import "./chunk-IX4JY6OO.js";
84
+ } from "./chunk-6V57JME6.js";
85
+ import "./chunk-QFROODUU.js";
86
86
  import "./chunk-TNEN6VQ2.js";
87
87
  import {
88
88
  errMsg
@@ -840,6 +840,21 @@ var waitForSelectorDef = {
840
840
  }
841
841
  };
842
842
  var waitCommand = registerCommand({ name: "wait", selectorParams: ["selector"], ...waitForSelectorDef });
843
+ var waitForTimeoutCommand = registerCommand({
844
+ name: "waitForTimeout",
845
+ description: "Wait for a specified number of milliseconds",
846
+ scope: "project",
847
+ parameters: z4.object({
848
+ timeout: z4.number().describe("Milliseconds to wait").default(1e3)
849
+ }),
850
+ result: z4.object({
851
+ waited: z4.number()
852
+ }),
853
+ handler: async (p) => {
854
+ await new Promise((r) => setTimeout(r, p.timeout));
855
+ return ok4({ waited: p.timeout });
856
+ }
857
+ });
843
858
 
844
859
  // src/commands/scroll.ts
845
860
  import { z as z5 } from "zod";
@@ -1705,7 +1720,7 @@ var healthCheckCommand = registerCommand({
1705
1720
  issues.push({
1706
1721
  severity: "error",
1707
1722
  category: "links",
1708
- message: `Broken link (fetch error): ${href} \u2014 ${errMsg(err) || "unknown"}`
1723
+ message: `Broken link (fetch error): ${href} \u2014 ${(err instanceof Error ? err.message : String(err)) || "unknown"}`
1709
1724
  });
1710
1725
  }
1711
1726
  }
@@ -2381,9 +2396,53 @@ var scrapeCommand = registerCommand({
2381
2396
  }
2382
2397
  let content;
2383
2398
  switch (p.format) {
2384
- case "markdown":
2399
+ case "markdown": {
2400
+ const tablesMd = await page.evaluate(() => {
2401
+ const tableSelectors = [
2402
+ "table",
2403
+ '[role="table"]',
2404
+ '[role="grid"]',
2405
+ '[class*="el-table"]',
2406
+ // Element UI
2407
+ '[class*="ant-table"]',
2408
+ // Ant Design
2409
+ '[class*="MuiTable"]',
2410
+ // Material UI
2411
+ '[class*="table"]'
2412
+ // Generic table-like
2413
+ ].join(",");
2414
+ const tables = document.querySelectorAll(tableSelectors);
2415
+ if (tables.length === 0) return "";
2416
+ return Array.from(tables).map((table) => {
2417
+ const rows = table.querySelectorAll('tr, [role="row"], [class*="row"]');
2418
+ if (rows.length === 0) return "";
2419
+ const mdRows = Array.from(rows).map((row) => {
2420
+ const cells = row.querySelectorAll('th, td, [role="columnheader"], [role="cell"], [class*="cell"], [class*="col"]');
2421
+ return "| " + Array.from(cells).map((c) => {
2422
+ const cellText = c.innerText?.trim().replace(/\n/g, " ") || "";
2423
+ return cellText.replace(/\|/g, "\\|") || "";
2424
+ }).join(" | ") + " |";
2425
+ }).join("\n");
2426
+ const headerRow = rows[0];
2427
+ const headerCells = headerRow.querySelectorAll('th, [role="columnheader"], [class*="header"]');
2428
+ const hasHeader = headerCells.length > 0;
2429
+ if (hasHeader && mdRows) {
2430
+ const headerCount = headerCells.length;
2431
+ const sep = "| " + Array(headerCount).fill("---").join(" | ") + " |";
2432
+ return mdRows.split("\n").map((line, i) => {
2433
+ if (i === 0) return line + "\n" + sep;
2434
+ return line;
2435
+ }).join("\n");
2436
+ }
2437
+ return mdRows;
2438
+ }).join("\n\n");
2439
+ });
2385
2440
  content = htmlToMarkdown(html, { onlyMainContent: p.onlyMainContent });
2441
+ if (tablesMd) {
2442
+ content = tablesMd + "\n\n" + content;
2443
+ }
2386
2444
  break;
2445
+ }
2387
2446
  case "html":
2388
2447
  content = html;
2389
2448
  break;
@@ -5600,6 +5659,7 @@ function parseCommandChain(input, options) {
5600
5659
  continue;
5601
5660
  }
5602
5661
  if (char === ";") {
5662
+ lastOperator = "sequence";
5603
5663
  flushPipeline();
5604
5664
  continue;
5605
5665
  }
@@ -5654,6 +5714,7 @@ registerCommandDefinition("uncheck", ["selector"]);
5654
5714
  registerCommandDefinition("hover", ["selector"]);
5655
5715
  registerCommandDefinition("dblclick", ["selector"]);
5656
5716
  registerCommandDefinition("wait", ["selector"]);
5717
+ registerCommandDefinition("waitForTimeout", ["timeout"]);
5657
5718
  registerCommandDefinition("screenshot", []);
5658
5719
  registerCommandDefinition("eval", ["expression"]);
5659
5720
  registerCommandDefinition("scroll", ["direction"]);
@@ -7289,7 +7350,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
7289
7350
  }
7290
7351
  let targetPageOverride = null;
7291
7352
  if (_target && extraOpts?.cdpEndpoint) {
7292
- const { findTargetPage } = await import("./browser-5FR3B57B.js");
7353
+ const { findTargetPage } = await import("./browser-JP2LFPR2.js");
7293
7354
  targetPageOverride = await findTargetPage(extraOpts.cdpEndpoint, _target);
7294
7355
  if (!targetPageOverride) {
7295
7356
  return errorResult(`Target "${_target}" not found. Use 'xbrowser targets --cdp ${extraOpts.cdpEndpoint}' to list available pages.`);
@@ -9817,7 +9878,17 @@ var SELECTOR_COMMANDS = /* @__PURE__ */ new Set([
9817
9878
  "dblclick",
9818
9879
  "wait"
9819
9880
  ]);
9881
+ var CAMEL_TO_KEBAB = {
9882
+ getCookies: "get-cookies",
9883
+ setCookie: "set-cookie",
9884
+ clearCookies: "clear-cookies",
9885
+ getLocalStorage: "get-local-storage",
9886
+ setLocalStorage: "set-local-storage",
9887
+ clearLocalStorage: "clear-local-storage",
9888
+ setViewport: "set-viewport"
9889
+ };
9820
9890
  async function handleBrowserCommand(command, args, options, sessionName, mode, cdpEndpoint) {
9891
+ command = CAMEL_TO_KEBAB[command] || command;
9821
9892
  if (args.includes("--help") || args.includes("-h") || options.help || options.h) {
9822
9893
  const cmdDef = getCommand(command);
9823
9894
  if (cmdDef) {
@@ -9934,7 +10005,8 @@ async function handleBrowserCommand(command, args, options, sessionName, mode, c
9934
10005
  if (!args[0]) outputError(`Usage: xbrowser ${command} <url>`);
9935
10006
  cmdName = "goto";
9936
10007
  params = {
9937
- url: /^https?:\/\//i.test(args[0]) || /^wss?:\/\//i.test(args[0]) ? args[0] : "https://" + args[0],
10008
+ // Don't prefix if URL already has a scheme (http, file, about, data, etc.)
10009
+ url: /^(https?|wss?|file|about|data|chrome|blob):/i.test(args[0]) ? args[0] : /^[\w-]+(\.[\w-]+)+/.test(args[0]) || args[0].startsWith("localhost") ? "https://" + args[0] : args[0],
9938
10010
  waitUntil: options.waitUntil,
9939
10011
  ...options.timeout ? { timeout: Number(options.timeout) } : {}
9940
10012
  };
@@ -9946,7 +10018,7 @@ async function handleBrowserCommand(command, args, options, sessionName, mode, c
9946
10018
  type: options.type,
9947
10019
  selector: options.selector || options.s,
9948
10020
  base64: !!options.base64,
9949
- output: options.output || options.o
10021
+ output: options.output || options.o || args[0]
9950
10022
  };
9951
10023
  break;
9952
10024
  case "eval":
@@ -9992,11 +10064,12 @@ async function handleBrowserCommand(command, args, options, sessionName, mode, c
9992
10064
  break;
9993
10065
  }
9994
10066
  case "mouse": {
9995
- const action = options.action || args.find((a) => ["move", "click", "dblclick", "down", "up"].includes(a));
9996
- const actionIdx = action ? args.indexOf(action) : -1;
9997
- const x = options.x !== void 0 ? Number(options.x) : actionIdx >= 0 && args[actionIdx + 1] ? Number(args[actionIdx + 1]) : void 0;
9998
- const y = options.y !== void 0 ? Number(options.y) : actionIdx >= 0 && args[actionIdx + 2] ? Number(args[actionIdx + 2]) : void 0;
9999
- if (!action || x === void 0 || y === void 0) {
10067
+ const flatArgs = args.flatMap((a) => a.split(/\s+/).filter(Boolean));
10068
+ const action = options.action || flatArgs.find((a) => ["move", "click", "dblclick", "down", "up"].includes(a));
10069
+ const actionIdx = action ? flatArgs.indexOf(action) : -1;
10070
+ const x = options.x !== void 0 ? Number(options.x) : actionIdx >= 0 && flatArgs[actionIdx + 1] ? Number(flatArgs[actionIdx + 1]) : void 0;
10071
+ const y = options.y !== void 0 ? Number(options.y) : actionIdx >= 0 && flatArgs[actionIdx + 2] ? Number(flatArgs[actionIdx + 2]) : void 0;
10072
+ if (!action || x === void 0 || y === void 0 || isNaN(x) || isNaN(y)) {
10000
10073
  outputError("Usage: xbrowser mouse <move|click|dblclick> <x> <y>\n xbrowser mouse --action <action> --x <x> --y <y>");
10001
10074
  }
10002
10075
  cmdName = "mouse";
@@ -10182,6 +10255,7 @@ async function handleBrowserCommand(command, args, options, sessionName, mode, c
10182
10255
  outputResult(result.data, mode);
10183
10256
  console.error(`
10184
10257
  \u26A0\uFE0F ${hint}`);
10258
+ process.exit(1);
10185
10259
  } else {
10186
10260
  outputResult(result.data, mode);
10187
10261
  }
@@ -10276,11 +10350,6 @@ async function handleSession(args, options, mode, _cdpEndpoint) {
10276
10350
  }
10277
10351
 
10278
10352
  // src/cli/plugin-routes.ts
10279
- var pluginLoader3 = null;
10280
- function getPluginLoader2() {
10281
- if (!pluginLoader3) pluginLoader3 = new XBrowserPluginLoader();
10282
- return pluginLoader3;
10283
- }
10284
10353
  async function buildRuntimePluginInfo() {
10285
10354
  const loader = await getPluginLoader();
10286
10355
  const sites = loader.getCore().loader.getSites();
@@ -10538,6 +10607,10 @@ async function handlePlugin(args, options, mode) {
10538
10607
  } else {
10539
10608
  result = await installer.installWithMarketplaceFallback(source, installOpts);
10540
10609
  }
10610
+ try {
10611
+ await (await getPluginLoader()).reloadPlugin(result.name);
10612
+ } catch {
10613
+ }
10541
10614
  outputResult(
10542
10615
  { ok: true, name: result.name, source: result.source, path: result.path },
10543
10616
  mode
@@ -10553,6 +10626,11 @@ async function handlePlugin(args, options, mode) {
10553
10626
  outputError(`Plugin "${name}" is not installed. Use 'xbrowser plugin list' to see installed plugins.`);
10554
10627
  }
10555
10628
  await installer.uninstall(name);
10629
+ try {
10630
+ const loader = await getPluginLoader();
10631
+ await loader.reloadPlugin(name);
10632
+ } catch {
10633
+ }
10556
10634
  outputResult({ ok: true, name }, mode);
10557
10635
  break;
10558
10636
  }
@@ -10606,7 +10684,7 @@ Total: ${enrichedPlugins.length} plugins`);
10606
10684
  case "reload": {
10607
10685
  const name = subArgs[0];
10608
10686
  if (!name) outputError("Usage: xbrowser plugin reload <name>");
10609
- await getPluginLoader2().reloadPlugin(name);
10687
+ (await getPluginLoader()).reloadPlugin(name);
10610
10688
  outputResult({ ok: true, name }, mode);
10611
10689
  break;
10612
10690
  }
@@ -10990,15 +11068,20 @@ async function handleExtract(args, _mode) {
10990
11068
  console.log(`
10991
11069
  Saved LLM summary: ${outputPath}`);
10992
11070
  }
10993
- async function handleFilter(args, _mode) {
11071
+ async function handleFilter(args, _mode, options) {
10994
11072
  const filePath = args[0];
10995
11073
  const outputPath = args[1];
10996
11074
  if (!filePath || !outputPath) {
10997
- console.error("Usage: xbrowser filter <input.yaml> <output.yaml> [--exclude-types=type1,type2]");
11075
+ console.error("Usage: xbrowser filter <input.yaml> <output.yaml> [--exclude type1,type2]");
10998
11076
  process.exit(1);
10999
11077
  }
11000
11078
  const { filterRecording: filterRecording2, parseExcludeTypes: parseExcludeTypes2 } = await import("./filter-3JQWBM5F.js");
11001
- const excludeTypes = parseExcludeTypes2(args.slice(2));
11079
+ const excludeArgs = args.slice(2).concat(
11080
+ Object.entries(options || {}).flatMap(
11081
+ ([k, v]) => k.startsWith("exclude") ? [`--${k}${typeof v === "string" ? "=" + v : ""}`] : []
11082
+ )
11083
+ );
11084
+ const excludeTypes = parseExcludeTypes2(excludeArgs);
11002
11085
  const result = filterRecording2(filePath, outputPath, excludeTypes);
11003
11086
  console.log(`Filtered ${filePath} -> ${outputPath}`);
11004
11087
  console.log(` Original: ${result.originalCount}, After: ${result.filteredCount}, Removed: ${result.removed} (${result.percentage}%)`);
@@ -12449,10 +12532,18 @@ function extractCdpFromArgv(argv) {
12449
12532
  async function handleStdinMode(stdinCommands, argv) {
12450
12533
  const chain = stdinCommands.join(" && ");
12451
12534
  const cdpEndpoint = argv ? extractCdpFromArgv(argv) : void 0;
12452
- const chainResult = await executeChain(chain, { fileMode: true, cdpEndpoint });
12535
+ const sessionName = argv ? extractSessionNameFromArgv(argv) : "default";
12536
+ const chainResult = await executeChain(chain, { fileMode: true, cdpEndpoint, sessionName });
12453
12537
  printChainResult(chainResult);
12454
12538
  if (!chainResult.success) throw new Error("Command failed");
12455
12539
  }
12540
+ function extractSessionNameFromArgv(argv) {
12541
+ for (let i = 0; i < argv.length; i++) {
12542
+ if (argv[i] === "--session" && argv[i + 1]) return argv[i + 1];
12543
+ if (typeof argv[i] === "string" && argv[i].startsWith("--session=")) return argv[i].slice(10);
12544
+ }
12545
+ return process.env.XBROWSER_SESSION || "default";
12546
+ }
12456
12547
  async function handleEvalMode(argv) {
12457
12548
  const evalCommands = parseEvalFlags(argv);
12458
12549
  if (evalCommands.length === 0) return;
@@ -12592,6 +12683,31 @@ async function routeCommand(argvIn, stdinCommands) {
12592
12683
  }
12593
12684
  return;
12594
12685
  }
12686
+ const SUBCOMMAND_HELP = {
12687
+ session: "session open|close|kill|list [--session <name>] [--cdp <endpoint>]",
12688
+ plugin: "plugin install|uninstall|list|reload|schema|search|info <name>",
12689
+ record: "record start|stop|status [--url <url>] [--name <flow>]",
12690
+ daemon: "daemon status [--port <port>]",
12691
+ replay: "replay <file> [--slow-mo <ms>] [--stop-on-error]",
12692
+ create: "create <name> [--template static|dynamic|login|api]",
12693
+ run: "run <file>",
12694
+ serve: "serve [--port <port>] [--token <token>]",
12695
+ remote: "remote <url> [command] [--token <token>]",
12696
+ convert: "convert <file> [--to js|py|sh]",
12697
+ extract: "extract <file> [--format json|yaml]",
12698
+ filter: "filter <file> [--include <type>] [--exclude <type>]",
12699
+ test: "test <name> [--cdp <endpoint>]",
12700
+ viewer: "viewer [--session <name>]",
12701
+ kill: "kill [--all]",
12702
+ net: "net [--cdp <endpoint>]"
12703
+ };
12704
+ const subHelp = SUBCOMMAND_HELP[command];
12705
+ if (subHelp) {
12706
+ console.log(`
12707
+ Usage: xbrowser ${subHelp}
12708
+ `);
12709
+ return;
12710
+ }
12595
12711
  showMainHelp();
12596
12712
  return;
12597
12713
  }
@@ -12629,7 +12745,7 @@ async function routeCommand(argvIn, stdinCommands) {
12629
12745
  await handleExtract(cmdArgs, mode);
12630
12746
  break;
12631
12747
  case "filter":
12632
- await handleFilter(cmdArgs, mode);
12748
+ await handleFilter(cmdArgs, mode, options);
12633
12749
  break;
12634
12750
  case "run":
12635
12751
  if (!cmdArgs[0]) {
@@ -15865,7 +15981,7 @@ var DataCollector = class {
15865
15981
  return results;
15866
15982
  }
15867
15983
  async createBrowserContext() {
15868
- const { launch } = await import("./cdp-driver-LKNM6OQI.js");
15984
+ const { launch } = await import("./cdp-driver-S5STYUZZ.js");
15869
15985
  const { browser } = await launch({
15870
15986
  headless: true,
15871
15987
  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-LKNM6OQI.js");
34
+ const { launch } = await import("./cdp-driver-S5STYUZZ.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.1.1",
3
+ "version": "1.2.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": {
@@ -96,7 +96,7 @@
96
96
  "sharp": "^0.34.5",
97
97
  "turndown": "^7.2.4",
98
98
  "turndown-plugin-gfm": "^1.0.2",
99
- "ws": "^8.20.0",
99
+ "ws": "^8.21.0",
100
100
  "yaml": "^2.8.4",
101
101
  "zod": "^3.24.0"
102
102
  },