@xbrowser/cli 1.4.2 → 1.4.4

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.
@@ -20,7 +20,7 @@ import {
20
20
  saveSessionDiskMeta,
21
21
  setActivePage,
22
22
  touchSession
23
- } from "./chunk-TLOHV3FP.js";
23
+ } from "./chunk-CFPPWKVO.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-UZQYMXR5.js";
24
- import "./chunk-N4PIGZDG.js";
23
+ } from "./chunk-X3FKWJV4.js";
24
+ import "./chunk-IDJ5NILK.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-7OGKQWXE.js";
24
- import "./chunk-N4PIGZDG.js";
23
+ } from "./chunk-4W54GEMV.js";
24
+ import "./chunk-IDJ5NILK.js";
25
25
  import "./chunk-TNEN6VQ2.js";
26
26
  import "./chunk-GDKLH7ZY.js";
27
27
  import "./chunk-KFQGP6VL.js";
@@ -1167,11 +1167,21 @@ var XBPageImpl = class _XBPageImpl {
1167
1167
  }
1168
1168
  async goBack(opts = {}) {
1169
1169
  await this.evaluate("() => history.back()");
1170
- await this.waitForLoadState(opts.waitUntil ?? "load", opts.timeout);
1170
+ await this.waitForLoadState(opts.waitUntil ?? "domcontentloaded", opts.timeout ?? 5e3).catch(() => {
1171
+ });
1172
+ try {
1173
+ this._url = await this.evaluate("location.href");
1174
+ } catch {
1175
+ }
1171
1176
  }
1172
1177
  async goForward(opts = {}) {
1173
1178
  await this.evaluate("() => history.forward()");
1174
- await this.waitForLoadState(opts.waitUntil ?? "load", opts.timeout);
1179
+ await this.waitForLoadState(opts.waitUntil ?? "domcontentloaded", opts.timeout ?? 5e3).catch(() => {
1180
+ });
1181
+ try {
1182
+ this._url = await this.evaluate("location.href");
1183
+ } catch {
1184
+ }
1175
1185
  }
1176
1186
  async reload(opts = {}) {
1177
1187
  this._loadState = { loadFired: false, domContentFired: false, networkIdle: false };
@@ -1629,16 +1639,16 @@ Last error: ${lastError.message}` : "";
1629
1639
  const result = await this.conn.send("Page.getFrameTree", void 0, this.sessionId);
1630
1640
  const frames = [];
1631
1641
  const collect = (node) => {
1632
- frames.push({
1642
+ const frame = {
1633
1643
  name: () => node.frame.name || "",
1634
1644
  url: () => node.frame.url,
1635
- // Helper for frame.ts to pick by index
1636
- childFrames: () => (node.childFrames || []).map((c) => ({
1637
- name: () => c.frame.name || "",
1638
- url: () => c.frame.url,
1639
- childFrames: () => []
1640
- }))
1641
- });
1645
+ isDetached: () => false,
1646
+ page: () => this,
1647
+ evaluate: (fn, ...args) => this.evaluate(fn, ...args),
1648
+ $: (sel) => this.$(sel),
1649
+ $$: (sel) => this.$$(sel)
1650
+ };
1651
+ frames.push(frame);
1642
1652
  for (const child of node.childFrames || []) {
1643
1653
  collect({ frame: child.frame, childFrames: [] });
1644
1654
  }
@@ -14,7 +14,7 @@ import {
14
14
  scrollIntoView,
15
15
  waitForActionable,
16
16
  waitForNetworkIdle
17
- } from "./chunk-N4PIGZDG.js";
17
+ } from "./chunk-IDJ5NILK.js";
18
18
  import {
19
19
  connectToCDP,
20
20
  findChrome,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  launch
3
- } from "./chunk-N4PIGZDG.js";
3
+ } from "./chunk-IDJ5NILK.js";
4
4
  import {
5
5
  errMsg
6
6
  } from "./chunk-GDKLH7ZY.js";
@@ -1172,11 +1172,21 @@ var XBPageImpl = class _XBPageImpl {
1172
1172
  }
1173
1173
  async goBack(opts = {}) {
1174
1174
  await this.evaluate("() => history.back()");
1175
- await this.waitForLoadState(opts.waitUntil ?? "load", opts.timeout);
1175
+ await this.waitForLoadState(opts.waitUntil ?? "domcontentloaded", opts.timeout ?? 5e3).catch(() => {
1176
+ });
1177
+ try {
1178
+ this._url = await this.evaluate("location.href");
1179
+ } catch {
1180
+ }
1176
1181
  }
1177
1182
  async goForward(opts = {}) {
1178
1183
  await this.evaluate("() => history.forward()");
1179
- await this.waitForLoadState(opts.waitUntil ?? "load", opts.timeout);
1184
+ await this.waitForLoadState(opts.waitUntil ?? "domcontentloaded", opts.timeout ?? 5e3).catch(() => {
1185
+ });
1186
+ try {
1187
+ this._url = await this.evaluate("location.href");
1188
+ } catch {
1189
+ }
1180
1190
  }
1181
1191
  async reload(opts = {}) {
1182
1192
  this._loadState = { loadFired: false, domContentFired: false, networkIdle: false };
@@ -1634,16 +1644,16 @@ Last error: ${lastError.message}` : "";
1634
1644
  const result = await this.conn.send("Page.getFrameTree", void 0, this.sessionId);
1635
1645
  const frames = [];
1636
1646
  const collect = (node) => {
1637
- frames.push({
1647
+ const frame = {
1638
1648
  name: () => node.frame.name || "",
1639
1649
  url: () => node.frame.url,
1640
- // Helper for frame.ts to pick by index
1641
- childFrames: () => (node.childFrames || []).map((c) => ({
1642
- name: () => c.frame.name || "",
1643
- url: () => c.frame.url,
1644
- childFrames: () => []
1645
- }))
1646
- });
1650
+ isDetached: () => false,
1651
+ page: () => this,
1652
+ evaluate: (fn, ...args) => this.evaluate(fn, ...args),
1653
+ $: (sel) => this.$(sel),
1654
+ $$: (sel) => this.$$(sel)
1655
+ };
1656
+ frames.push(frame);
1647
1657
  for (const child of node.childFrames || []) {
1648
1658
  collect({ frame: child.frame, childFrames: [] });
1649
1659
  }
@@ -1166,11 +1166,21 @@ var XBPageImpl = class _XBPageImpl {
1166
1166
  }
1167
1167
  async goBack(opts = {}) {
1168
1168
  await this.evaluate("() => history.back()");
1169
- await this.waitForLoadState(opts.waitUntil ?? "load", opts.timeout);
1169
+ await this.waitForLoadState(opts.waitUntil ?? "domcontentloaded", opts.timeout ?? 5e3).catch(() => {
1170
+ });
1171
+ try {
1172
+ this._url = await this.evaluate("location.href");
1173
+ } catch {
1174
+ }
1170
1175
  }
1171
1176
  async goForward(opts = {}) {
1172
1177
  await this.evaluate("() => history.forward()");
1173
- await this.waitForLoadState(opts.waitUntil ?? "load", opts.timeout);
1178
+ await this.waitForLoadState(opts.waitUntil ?? "domcontentloaded", opts.timeout ?? 5e3).catch(() => {
1179
+ });
1180
+ try {
1181
+ this._url = await this.evaluate("location.href");
1182
+ } catch {
1183
+ }
1174
1184
  }
1175
1185
  async reload(opts = {}) {
1176
1186
  this._loadState = { loadFired: false, domContentFired: false, networkIdle: false };
@@ -1628,16 +1638,16 @@ Last error: ${lastError.message}` : "";
1628
1638
  const result = await this.conn.send("Page.getFrameTree", void 0, this.sessionId);
1629
1639
  const frames = [];
1630
1640
  const collect = (node) => {
1631
- frames.push({
1641
+ const frame = {
1632
1642
  name: () => node.frame.name || "",
1633
1643
  url: () => node.frame.url,
1634
- // Helper for frame.ts to pick by index
1635
- childFrames: () => (node.childFrames || []).map((c) => ({
1636
- name: () => c.frame.name || "",
1637
- url: () => c.frame.url,
1638
- childFrames: () => []
1639
- }))
1640
- });
1644
+ isDetached: () => false,
1645
+ page: () => this,
1646
+ evaluate: (fn, ...args) => this.evaluate(fn, ...args),
1647
+ $: (sel) => this.$(sel),
1648
+ $$: (sel) => this.$$(sel)
1649
+ };
1650
+ frames.push(frame);
1641
1651
  for (const child of node.childFrames || []) {
1642
1652
  collect({ frame: child.frame, childFrames: [] });
1643
1653
  }
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  launch
3
- } from "./chunk-N4PIGZDG.js";
3
+ } from "./chunk-IDJ5NILK.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-TLOHV3FP.js";
28
+ } from "./chunk-CFPPWKVO.js";
29
29
  import "./chunk-TNEN6VQ2.js";
30
30
  import {
31
31
  forwardCommandLog,
@@ -376,7 +376,8 @@ var backCommand = registerCommand({
376
376
  result: z.object({ url: z.string() }),
377
377
  handler: async (_p, ctx) => {
378
378
  await ctx.page.goBack();
379
- return ok({ url: ctx.page.url() });
379
+ const url = await ctx.page.evaluate("location.href").catch(() => ctx.page.url());
380
+ return ok({ url });
380
381
  }
381
382
  });
382
383
  var forwardCommand = registerCommand({
@@ -386,7 +387,8 @@ var forwardCommand = registerCommand({
386
387
  result: z.object({ url: z.string() }),
387
388
  handler: async (_p, ctx) => {
388
389
  await ctx.page.goForward();
389
- return ok({ url: ctx.page.url() });
390
+ const url = await ctx.page.evaluate("location.href").catch(() => ctx.page.url());
391
+ return ok({ url });
390
392
  }
391
393
  });
392
394
  var refreshCommand = registerCommand({
@@ -2678,7 +2680,7 @@ async function discoverUrls(page, baseUrl, options) {
2678
2680
  `);
2679
2681
  const basePath = new URL(baseUrl).pathname;
2680
2682
  let filtered = Array.from(allUrls).filter(
2681
- (u) => isSameDomain(u, baseHostname, options.includeSubdomains ?? false) && isWithinPathScope(u, basePath)
2683
+ (u) => options.allowExternalLinks || isSameDomain(u, baseHostname, options.includeSubdomains ?? false) && isWithinPathScope(u, basePath)
2682
2684
  );
2683
2685
  filtered = deduplicateUrls(filtered);
2684
2686
  if (options.search) {
@@ -2699,6 +2701,7 @@ var mapCommand = registerCommand({
2699
2701
  search: z16.string().optional(),
2700
2702
  sitemap: z16.enum(["include", "only"]).optional(),
2701
2703
  includeSubdomains: z16.boolean().optional(),
2704
+ allowExternalLinks: z16.boolean().optional().describe("Include links to external domains"),
2702
2705
  limit: z16.number().optional(),
2703
2706
  verbose: z16.boolean().default(false).describe("Show progress feedback")
2704
2707
  }),
@@ -2708,6 +2711,7 @@ var mapCommand = registerCommand({
2708
2711
  const links = await discoverUrls(page, p.url, {
2709
2712
  sitemap: p.sitemap,
2710
2713
  includeSubdomains: p.includeSubdomains,
2714
+ allowExternalLinks: p.allowExternalLinks,
2711
2715
  limit: p.limit,
2712
2716
  search: p.search,
2713
2717
  verbose: p.verbose
@@ -4892,8 +4896,23 @@ async function waitForPage(page, input) {
4892
4896
  return { success: true, matched: "selector", timeout, elapsed: Date.now() - startedAt };
4893
4897
  }
4894
4898
  if (input.text) {
4895
- await page.getByText(input.text).first().waitFor({ state: "visible", timeout });
4896
- return { success: true, matched: "text", timeout, elapsed: Date.now() - startedAt };
4899
+ const textToFind = input.text;
4900
+ const matched = await pollUntil(timeout, pollInterval, async () => {
4901
+ return page.evaluate((text) => {
4902
+ const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT);
4903
+ while (walker.nextNode()) {
4904
+ if (walker.currentNode.textContent?.includes(text)) return true;
4905
+ }
4906
+ return false;
4907
+ }, textToFind);
4908
+ });
4909
+ return {
4910
+ success: matched,
4911
+ matched: "text",
4912
+ timeout,
4913
+ elapsed: Date.now() - startedAt,
4914
+ ...matched ? {} : { message: `Timed out waiting for text: ${input.text}` }
4915
+ };
4897
4916
  }
4898
4917
  if (input.url) {
4899
4918
  const matched = await pollUntil(timeout, pollInterval, async () => matchUrlPattern(page.url(), input.url));
@@ -7058,7 +7077,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
7058
7077
  }
7059
7078
  let targetPageOverride = null;
7060
7079
  if (_target && extraOpts?.cdpEndpoint) {
7061
- const { findTargetPage } = await import("./browser-VKZVOOVV.js");
7080
+ const { findTargetPage } = await import("./browser-DS24BWJW.js");
7062
7081
  targetPageOverride = await findTargetPage(extraOpts.cdpEndpoint, _target);
7063
7082
  if (!targetPageOverride) {
7064
7083
  return errorResult(`Target "${_target}" not found. Use 'xbrowser targets --cdp ${extraOpts.cdpEndpoint}' to list available pages.`);
@@ -10009,6 +10028,12 @@ async function handleBrowserCommand(command, args, options, sessionName, mode, c
10009
10028
  };
10010
10029
  break;
10011
10030
  }
10031
+ case "waitForTimeout":
10032
+ cmdName = "waitForTimeout";
10033
+ params = {
10034
+ timeout: args[0] ? Number(args[0]) : options.timeout ? Number(options.timeout) : 1e3
10035
+ };
10036
+ break;
10012
10037
  default:
10013
10038
  cmdName = command;
10014
10039
  params = { ...options };
@@ -12408,7 +12433,7 @@ async function routeCommand(argvIn, stdinCommands) {
12408
12433
  const possibleCmd = argv[0].substring(0, spaceIdx);
12409
12434
  if (/^[a-zA-Z][\w-]*$/.test(possibleCmd)) {
12410
12435
  const remainder = argv[0].substring(spaceIdx + 1);
12411
- if (remainder.startsWith("--") || remainder.includes(" --") && !remainder.match(/^\w+:\/\//)) {
12436
+ if (remainder.includes("--")) {
12412
12437
  const remainderParts = remainder.split(/\s+/).filter(Boolean);
12413
12438
  argv = [possibleCmd, ...remainderParts, ...argv.slice(1)];
12414
12439
  } else {
@@ -12427,7 +12452,7 @@ async function routeCommand(argvIn, stdinCommands) {
12427
12452
  const mode = options.json ? "json" : options.yaml ? "yaml" : "text";
12428
12453
  const sessionName = options.session || process.env.XBROWSER_SESSION || "default";
12429
12454
  const cdpEndpoint = options.cdp || process.env.XBROWSER_CDP;
12430
- if (options.version || options.v) {
12455
+ if (options.version) {
12431
12456
  console.log(`xbrowser v${version}`);
12432
12457
  return;
12433
12458
  }
@@ -13024,7 +13049,7 @@ async function main() {
13024
13049
  const command = process.argv[2];
13025
13050
  const isLongRunning = command === "preview" || command === "serve";
13026
13051
  if (!isLongRunning) {
13027
- const { ensureProcessCanExit } = await import("./browser-VKZVOOVV.js");
13052
+ const { ensureProcessCanExit } = await import("./browser-DS24BWJW.js");
13028
13053
  await ensureProcessCanExit().catch(() => {
13029
13054
  });
13030
13055
  process.exit(exitCode);
@@ -21,8 +21,8 @@ import {
21
21
  resolveLaunchOpts,
22
22
  saveSessionDiskMeta,
23
23
  setActivePage
24
- } from "./chunk-7OGKQWXE.js";
25
- import "./chunk-N4PIGZDG.js";
24
+ } from "./chunk-4W54GEMV.js";
25
+ import "./chunk-IDJ5NILK.js";
26
26
  import "./chunk-TNEN6VQ2.js";
27
27
  import {
28
28
  getPluginLoader
@@ -337,7 +337,8 @@ var backCommand = registerCommand({
337
337
  result: z.object({ url: z.string() }),
338
338
  handler: async (_p, ctx) => {
339
339
  await ctx.page.goBack();
340
- return ok({ url: ctx.page.url() });
340
+ const url = await ctx.page.evaluate("location.href").catch(() => ctx.page.url());
341
+ return ok({ url });
341
342
  }
342
343
  });
343
344
  var forwardCommand = registerCommand({
@@ -347,7 +348,8 @@ var forwardCommand = registerCommand({
347
348
  result: z.object({ url: z.string() }),
348
349
  handler: async (_p, ctx) => {
349
350
  await ctx.page.goForward();
350
- return ok({ url: ctx.page.url() });
351
+ const url = await ctx.page.evaluate("location.href").catch(() => ctx.page.url());
352
+ return ok({ url });
351
353
  }
352
354
  });
353
355
  var refreshCommand = registerCommand({
@@ -2639,7 +2641,7 @@ async function discoverUrls(page, baseUrl, options) {
2639
2641
  `);
2640
2642
  const basePath = new URL(baseUrl).pathname;
2641
2643
  let filtered = Array.from(allUrls).filter(
2642
- (u) => isSameDomain(u, baseHostname, options.includeSubdomains ?? false) && isWithinPathScope(u, basePath)
2644
+ (u) => options.allowExternalLinks || isSameDomain(u, baseHostname, options.includeSubdomains ?? false) && isWithinPathScope(u, basePath)
2643
2645
  );
2644
2646
  filtered = deduplicateUrls(filtered);
2645
2647
  if (options.search) {
@@ -2660,6 +2662,7 @@ var mapCommand = registerCommand({
2660
2662
  search: z16.string().optional(),
2661
2663
  sitemap: z16.enum(["include", "only"]).optional(),
2662
2664
  includeSubdomains: z16.boolean().optional(),
2665
+ allowExternalLinks: z16.boolean().optional().describe("Include links to external domains"),
2663
2666
  limit: z16.number().optional(),
2664
2667
  verbose: z16.boolean().default(false).describe("Show progress feedback")
2665
2668
  }),
@@ -2669,6 +2672,7 @@ var mapCommand = registerCommand({
2669
2672
  const links = await discoverUrls(page, p.url, {
2670
2673
  sitemap: p.sitemap,
2671
2674
  includeSubdomains: p.includeSubdomains,
2675
+ allowExternalLinks: p.allowExternalLinks,
2672
2676
  limit: p.limit,
2673
2677
  search: p.search,
2674
2678
  verbose: p.verbose
@@ -4853,8 +4857,23 @@ async function waitForPage(page, input) {
4853
4857
  return { success: true, matched: "selector", timeout, elapsed: Date.now() - startedAt };
4854
4858
  }
4855
4859
  if (input.text) {
4856
- await page.getByText(input.text).first().waitFor({ state: "visible", timeout });
4857
- return { success: true, matched: "text", timeout, elapsed: Date.now() - startedAt };
4860
+ const textToFind = input.text;
4861
+ const matched = await pollUntil(timeout, pollInterval, async () => {
4862
+ return page.evaluate((text) => {
4863
+ const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT);
4864
+ while (walker.nextNode()) {
4865
+ if (walker.currentNode.textContent?.includes(text)) return true;
4866
+ }
4867
+ return false;
4868
+ }, textToFind);
4869
+ });
4870
+ return {
4871
+ success: matched,
4872
+ matched: "text",
4873
+ timeout,
4874
+ elapsed: Date.now() - startedAt,
4875
+ ...matched ? {} : { message: `Timed out waiting for text: ${input.text}` }
4876
+ };
4858
4877
  }
4859
4878
  if (input.url) {
4860
4879
  const matched = await pollUntil(timeout, pollInterval, async () => matchUrlPattern(page.url(), input.url));
@@ -6589,7 +6608,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
6589
6608
  }
6590
6609
  let targetPageOverride = null;
6591
6610
  if (_target && extraOpts?.cdpEndpoint) {
6592
- const { findTargetPage } = await import("./browser-ALV2PLW4.js");
6611
+ const { findTargetPage } = await import("./browser-YKJO3BOQ.js");
6593
6612
  targetPageOverride = await findTargetPage(extraOpts.cdpEndpoint, _target);
6594
6613
  if (!targetPageOverride) {
6595
6614
  return errorResult(`Target "${_target}" not found. Use 'xbrowser targets --cdp ${extraOpts.cdpEndpoint}' to list available pages.`);
@@ -8305,7 +8324,7 @@ function createRPCHandler() {
8305
8324
  const isNewFormat = Array.isArray(parsed.actions);
8306
8325
  if (isNewFormat) {
8307
8326
  try {
8308
- const { SessionReplayer } = await import("./session-replayer-JIVFXVUL.js");
8327
+ const { SessionReplayer } = await import("./session-replayer-F4ORJMCL.js");
8309
8328
  const replayer = new SessionReplayer({
8310
8329
  page: session.page,
8311
8330
  stepDelay: slowMo * 500,
package/dist/index.d.ts CHANGED
@@ -172,6 +172,7 @@ interface XBPage {
172
172
  height: number;
173
173
  }): Promise<void>;
174
174
  addInitScript(script: string): Promise<void>;
175
+ discoverFrames(): Promise<XBFrame[]>;
175
176
  bringToFront(): Promise<void>;
176
177
  setExtraHTTPHeaders(headers: Record<string, string>): Promise<void>;
177
178
  setOfflineMode(offline: boolean): Promise<void>;
@@ -1606,6 +1607,10 @@ declare class RecorderController {
1606
1607
  private getDefaultOutputPath;
1607
1608
  }
1608
1609
 
1610
+ /**
1611
+ * Recording-related type definitions.
1612
+ * Extracted from session-recorder.ts to keep the main file focused on logic.
1613
+ */
1609
1614
  interface ClickContextItem {
1610
1615
  text: string;
1611
1616
  tag?: string;
@@ -1650,17 +1655,13 @@ interface UserAction {
1650
1655
  tag: string;
1651
1656
  selector?: string;
1652
1657
  text: string;
1653
- /** Which strategy from generateUniqueSelector produced this selector */
1654
1658
  strategy?: string;
1655
- /** Reliability rating from generateUniqueSelector: high / medium / low */
1656
1659
  confidence?: 'high' | 'medium' | 'low';
1657
- /** Text-based fallback for low-confidence selectors (e.g. menu items) */
1658
1660
  textFallback?: {
1659
1661
  type: 'text';
1660
1662
  value: string;
1661
1663
  selector: string;
1662
1664
  };
1663
- /** Popup/menu context when element is inside a dropdown */
1664
1665
  popup?: {
1665
1666
  containerSelector: string;
1666
1667
  containerText: string;
@@ -1677,9 +1678,7 @@ interface UserAction {
1677
1678
  y?: number;
1678
1679
  scrollX?: number;
1679
1680
  scrollY?: number;
1680
- /** Click context: popover/dropdown/menu items captured 200ms after click */
1681
1681
  clickContext?: ClickContext;
1682
- /** File upload info (type=filechooser only) */
1683
1682
  files?: {
1684
1683
  names: string[];
1685
1684
  count: number;
@@ -1691,36 +1690,30 @@ interface UserAction {
1691
1690
  dataUrl: string | null;
1692
1691
  }>;
1693
1692
  };
1694
- /** Drag & drop info (type=drag only) */
1695
1693
  drag?: {
1696
1694
  fromX: number;
1697
1695
  fromY: number;
1698
1696
  toX: number;
1699
1697
  toY: number;
1700
- /** Source element description */
1701
1698
  source?: {
1702
1699
  tag: string;
1703
1700
  selector?: string;
1704
1701
  text: string;
1705
1702
  };
1706
- /** Target (drop zone) element description */
1707
1703
  target?: {
1708
1704
  tag: string;
1709
1705
  selector?: string;
1710
1706
  text: string;
1711
1707
  };
1712
1708
  };
1713
- /** Resize info (type=resize only) */
1714
1709
  resize?: {
1715
1710
  width: number;
1716
1711
  height: number;
1717
1712
  };
1718
- /** Clipboard info (type=clipboard only) */
1719
1713
  clipboard?: {
1720
1714
  operation: 'copy' | 'paste' | 'cut';
1721
1715
  textPreview?: string;
1722
1716
  };
1723
- /** Touch info (type=touch only) */
1724
1717
  touch?: {
1725
1718
  touchType: 'start' | 'move' | 'end';
1726
1719
  touches: Array<{
@@ -1728,26 +1721,19 @@ interface UserAction {
1728
1721
  y: number;
1729
1722
  }>;
1730
1723
  };
1731
- /** Focus info (type=focus only) */
1732
1724
  focus?: {
1733
1725
  focusType: 'focus' | 'blur';
1734
1726
  };
1735
- /** Visibility info (type=visibility only) */
1736
1727
  visibility?: {
1737
1728
  state: 'visible' | 'hidden';
1738
1729
  };
1739
- /** Mouse trajectory from previous action's position to this action's position.
1740
- * Captured as simplified waypoints with relative timestamps for realistic replay. */
1741
1730
  trajectory?: {
1742
- /** Waypoints: [x, y, deltaMs from previous point] */
1743
1731
  points: Array<{
1744
1732
  x: number;
1745
1733
  y: number;
1746
1734
  dt: number;
1747
1735
  }>;
1748
- /** Total distance in pixels (approximate) */
1749
1736
  distance: number;
1750
- /** Total duration in ms */
1751
1737
  duration: number;
1752
1738
  };
1753
1739
  }
@@ -1825,13 +1811,13 @@ interface RecordingData {
1825
1811
  contextChanges: ContextChange[];
1826
1812
  checkpoints: CheckpointEntry[];
1827
1813
  }
1828
- /** Written to disk so `record stop` (separate process) can signal the recorder. */
1829
1814
  interface RecordingControlFile {
1830
1815
  pid: number;
1831
1816
  startedAt: string;
1832
1817
  startUrl: string;
1833
1818
  sessionName: string;
1834
1819
  }
1820
+
1835
1821
  declare class SessionRecorder {
1836
1822
  private context;
1837
1823
  private page;
package/dist/index.js CHANGED
@@ -81,8 +81,8 @@ import {
81
81
  resolveLaunchOpts,
82
82
  saveSessionDiskMeta,
83
83
  setActivePage
84
- } from "./chunk-UZQYMXR5.js";
85
- import "./chunk-N4PIGZDG.js";
84
+ } from "./chunk-X3FKWJV4.js";
85
+ import "./chunk-IDJ5NILK.js";
86
86
  import "./chunk-TNEN6VQ2.js";
87
87
  import {
88
88
  errMsg
@@ -416,7 +416,8 @@ var backCommand = registerCommand({
416
416
  result: z.object({ url: z.string() }),
417
417
  handler: async (_p, ctx) => {
418
418
  await ctx.page.goBack();
419
- return ok({ url: ctx.page.url() });
419
+ const url = await ctx.page.evaluate("location.href").catch(() => ctx.page.url());
420
+ return ok({ url });
420
421
  }
421
422
  });
422
423
  var forwardCommand = registerCommand({
@@ -426,7 +427,8 @@ var forwardCommand = registerCommand({
426
427
  result: z.object({ url: z.string() }),
427
428
  handler: async (_p, ctx) => {
428
429
  await ctx.page.goForward();
429
- return ok({ url: ctx.page.url() });
430
+ const url = await ctx.page.evaluate("location.href").catch(() => ctx.page.url());
431
+ return ok({ url });
430
432
  }
431
433
  });
432
434
  var refreshCommand = registerCommand({
@@ -2718,7 +2720,7 @@ async function discoverUrls(page, baseUrl, options) {
2718
2720
  `);
2719
2721
  const basePath = new URL(baseUrl).pathname;
2720
2722
  let filtered = Array.from(allUrls).filter(
2721
- (u) => isSameDomain(u, baseHostname, options.includeSubdomains ?? false) && isWithinPathScope(u, basePath)
2723
+ (u) => options.allowExternalLinks || isSameDomain(u, baseHostname, options.includeSubdomains ?? false) && isWithinPathScope(u, basePath)
2722
2724
  );
2723
2725
  filtered = deduplicateUrls(filtered);
2724
2726
  if (options.search) {
@@ -2739,6 +2741,7 @@ var mapCommand = registerCommand({
2739
2741
  search: z16.string().optional(),
2740
2742
  sitemap: z16.enum(["include", "only"]).optional(),
2741
2743
  includeSubdomains: z16.boolean().optional(),
2744
+ allowExternalLinks: z16.boolean().optional().describe("Include links to external domains"),
2742
2745
  limit: z16.number().optional(),
2743
2746
  verbose: z16.boolean().default(false).describe("Show progress feedback")
2744
2747
  }),
@@ -2748,6 +2751,7 @@ var mapCommand = registerCommand({
2748
2751
  const links = await discoverUrls(page, p.url, {
2749
2752
  sitemap: p.sitemap,
2750
2753
  includeSubdomains: p.includeSubdomains,
2754
+ allowExternalLinks: p.allowExternalLinks,
2751
2755
  limit: p.limit,
2752
2756
  search: p.search,
2753
2757
  verbose: p.verbose
@@ -5209,8 +5213,23 @@ async function waitForPage(page, input) {
5209
5213
  return { success: true, matched: "selector", timeout, elapsed: Date.now() - startedAt };
5210
5214
  }
5211
5215
  if (input.text) {
5212
- await page.getByText(input.text).first().waitFor({ state: "visible", timeout });
5213
- return { success: true, matched: "text", timeout, elapsed: Date.now() - startedAt };
5216
+ const textToFind = input.text;
5217
+ const matched = await pollUntil(timeout, pollInterval, async () => {
5218
+ return page.evaluate((text) => {
5219
+ const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT);
5220
+ while (walker.nextNode()) {
5221
+ if (walker.currentNode.textContent?.includes(text)) return true;
5222
+ }
5223
+ return false;
5224
+ }, textToFind);
5225
+ });
5226
+ return {
5227
+ success: matched,
5228
+ matched: "text",
5229
+ timeout,
5230
+ elapsed: Date.now() - startedAt,
5231
+ ...matched ? {} : { message: `Timed out waiting for text: ${input.text}` }
5232
+ };
5214
5233
  }
5215
5234
  if (input.url) {
5216
5235
  const matched = await pollUntil(timeout, pollInterval, async () => matchUrlPattern(page.url(), input.url));
@@ -7378,7 +7397,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
7378
7397
  }
7379
7398
  let targetPageOverride = null;
7380
7399
  if (_target && extraOpts?.cdpEndpoint) {
7381
- const { findTargetPage } = await import("./browser-WMQRPYXX.js");
7400
+ const { findTargetPage } = await import("./browser-KBUORWR3.js");
7382
7401
  targetPageOverride = await findTargetPage(extraOpts.cdpEndpoint, _target);
7383
7402
  if (!targetPageOverride) {
7384
7403
  return errorResult(`Target "${_target}" not found. Use 'xbrowser targets --cdp ${extraOpts.cdpEndpoint}' to list available pages.`);
@@ -10349,6 +10368,12 @@ async function handleBrowserCommand(command, args, options, sessionName, mode, c
10349
10368
  };
10350
10369
  break;
10351
10370
  }
10371
+ case "waitForTimeout":
10372
+ cmdName = "waitForTimeout";
10373
+ params = {
10374
+ timeout: args[0] ? Number(args[0]) : options.timeout ? Number(options.timeout) : 1e3
10375
+ };
10376
+ break;
10352
10377
  default:
10353
10378
  cmdName = command;
10354
10379
  params = { ...options };
@@ -12748,7 +12773,7 @@ async function routeCommand(argvIn, stdinCommands) {
12748
12773
  const possibleCmd = argv[0].substring(0, spaceIdx);
12749
12774
  if (/^[a-zA-Z][\w-]*$/.test(possibleCmd)) {
12750
12775
  const remainder = argv[0].substring(spaceIdx + 1);
12751
- if (remainder.startsWith("--") || remainder.includes(" --") && !remainder.match(/^\w+:\/\//)) {
12776
+ if (remainder.includes("--")) {
12752
12777
  const remainderParts = remainder.split(/\s+/).filter(Boolean);
12753
12778
  argv = [possibleCmd, ...remainderParts, ...argv.slice(1)];
12754
12779
  } else {
@@ -12767,7 +12792,7 @@ async function routeCommand(argvIn, stdinCommands) {
12767
12792
  const mode = options.json ? "json" : options.yaml ? "yaml" : "text";
12768
12793
  const sessionName = options.session || process.env.XBROWSER_SESSION || "default";
12769
12794
  const cdpEndpoint = options.cdp || process.env.XBROWSER_CDP;
12770
- if (options.version || options.v) {
12795
+ if (options.version) {
12771
12796
  console.log(`xbrowser v${version}`);
12772
12797
  return;
12773
12798
  }
@@ -16151,7 +16176,7 @@ var DataCollector = class {
16151
16176
  return results;
16152
16177
  }
16153
16178
  async createBrowserContext() {
16154
- const { launch } = await import("./cdp-driver-72HOBP4C.js");
16179
+ const { launch } = await import("./cdp-driver-VRXHK6P6.js");
16155
16180
  const { browser } = await launch({
16156
16181
  headless: true,
16157
16182
  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-72HOBP4C.js");
34
+ const { launch } = await import("./cdp-driver-VRXHK6P6.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.2",
3
+ "version": "1.4.4",
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": {
@@ -122,6 +122,6 @@
122
122
  ]
123
123
  },
124
124
  "optionalDependencies": {
125
- "undici": "^7.25.0"
125
+ "undici": "^7.28.0"
126
126
  }
127
127
  }