@xbrowser/cli 1.9.0 → 1.9.2

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.
@@ -26,7 +26,7 @@ import "./chunk-VJNMAWPZ.js";
26
26
  import "./chunk-TNEN6VQ2.js";
27
27
  import {
28
28
  getPluginLoader
29
- } from "./chunk-2QQDTXDL.js";
29
+ } from "./chunk-PIWNMC36.js";
30
30
  import {
31
31
  getDaemonConfig,
32
32
  getDaemonProcessStatus,
@@ -53,7 +53,7 @@ import {
53
53
  // src/executor.ts
54
54
  import {
55
55
  ok as ok25,
56
- fail as fail9,
56
+ fail as fail11,
57
57
  isCommandResult,
58
58
  CompositeStorage as CompositeStorage2,
59
59
  TipCollector as TipCollector2,
@@ -918,7 +918,7 @@ var evaluateCommand = registerCommand({
918
918
 
919
919
  // src/commands/storage.ts
920
920
  import { z as z8 } from "zod";
921
- import { ok as ok8 } from "@dyyz1993/xcli-core";
921
+ import { ok as ok8, fail as fail2 } from "@dyyz1993/xcli-core";
922
922
  var getCookiesCommand = registerCommand({
923
923
  name: "get-cookies",
924
924
  description: "Get all cookies for the current page",
@@ -940,6 +940,7 @@ var setCookieCommand = registerCommand({
940
940
  value: z8.coerce.string(),
941
941
  domain: z8.coerce.string().optional(),
942
942
  path: z8.coerce.string().optional(),
943
+ url: z8.string().optional().describe("Cookie URL (alternative to domain)"),
943
944
  expires: z8.number().optional(),
944
945
  httpOnly: z8.boolean().optional(),
945
946
  secure: z8.boolean().optional(),
@@ -959,6 +960,9 @@ var setCookieCommand = registerCommand({
959
960
  }
960
961
  }
961
962
  }
963
+ if (!cookie.domain && !cookie.url) {
964
+ return fail2("set-cookie requires --domain or --url, or a non-blank page URL to infer from");
965
+ }
962
966
  await ctx.browserContext.addCookies([cookie]);
963
967
  return ok8({ name: p.name });
964
968
  }
@@ -985,19 +989,23 @@ var getLocalStorageCommand = registerCommand({
985
989
  z8.object({ data: z8.record(z8.string()) })
986
990
  ]),
987
991
  handler: async (p, ctx) => {
988
- if (p.key) {
989
- const value = await ctx.page.evaluate((k) => localStorage.getItem(k), p.key);
990
- return ok8({ key: p.key, value });
992
+ try {
993
+ if (p.key) {
994
+ const value = await ctx.page.evaluate((k) => localStorage.getItem(k), p.key);
995
+ return ok8({ key: p.key, value });
996
+ }
997
+ const data = await ctx.page.evaluate(() => {
998
+ const entries = {};
999
+ for (let i = 0; i < localStorage.length; i++) {
1000
+ const key = localStorage.key(i);
1001
+ if (key) entries[key] = localStorage.getItem(key) ?? "";
1002
+ }
1003
+ return entries;
1004
+ });
1005
+ return ok8({ data });
1006
+ } catch (e) {
1007
+ return fail2(`localStorage not accessible on this page: ${e instanceof Error ? e.message : String(e)}`);
991
1008
  }
992
- const data = await ctx.page.evaluate(() => {
993
- const entries = {};
994
- for (let i = 0; i < localStorage.length; i++) {
995
- const key = localStorage.key(i);
996
- if (key) entries[key] = localStorage.getItem(key) ?? "";
997
- }
998
- return entries;
999
- });
1000
- return ok8({ data });
1001
1009
  }
1002
1010
  });
1003
1011
  var setLocalStorageCommand = registerCommand({
@@ -1010,13 +1018,17 @@ var setLocalStorageCommand = registerCommand({
1010
1018
  }),
1011
1019
  result: z8.object({ key: z8.string() }),
1012
1020
  handler: async (p, ctx) => {
1013
- await ctx.page.evaluate(
1014
- (args) => {
1015
- localStorage.setItem(args.key, args.value);
1016
- },
1017
- { key: p.key, value: p.value }
1018
- );
1019
- return ok8({ key: p.key });
1021
+ try {
1022
+ await ctx.page.evaluate(
1023
+ (args) => {
1024
+ localStorage.setItem(args.key, args.value);
1025
+ },
1026
+ { key: p.key, value: p.value }
1027
+ );
1028
+ return ok8({ key: p.key });
1029
+ } catch (e) {
1030
+ return fail2(`localStorage not accessible on this page: ${e instanceof Error ? e.message : String(e)}`);
1031
+ }
1020
1032
  }
1021
1033
  });
1022
1034
  var clearLocalStorageCommand = registerCommand({
@@ -1025,21 +1037,35 @@ var clearLocalStorageCommand = registerCommand({
1025
1037
  scope: "page",
1026
1038
  result: z8.object({ cleared: z8.boolean() }),
1027
1039
  handler: async (_p, ctx) => {
1028
- await ctx.page.evaluate(() => localStorage.clear());
1029
- return ok8({ cleared: true });
1040
+ try {
1041
+ await ctx.page.evaluate(() => localStorage.clear());
1042
+ return ok8({ cleared: true });
1043
+ } catch (e) {
1044
+ return fail2(`localStorage not accessible on this page: ${e instanceof Error ? e.message : String(e)}`);
1045
+ }
1030
1046
  }
1031
1047
  });
1032
1048
 
1033
1049
  // src/commands/screenshot.ts
1034
1050
  import { z as z9 } from "zod";
1035
- import { ok as ok9 } from "@dyyz1993/xcli-core";
1051
+ import { ok as ok9, fail as fail3 } from "@dyyz1993/xcli-core";
1036
1052
  import { writeFileSync, mkdirSync } from "fs";
1037
- import { join as join2 } from "path";
1053
+ import { dirname, join as join2 } from "path";
1038
1054
  import { homedir as homedir2 } from "os";
1039
1055
  var SCREENSHOTS_DIR = join2(homedir2(), ".xbrowser", "screenshots");
1040
1056
  function ensureScreenshotsDir() {
1041
1057
  mkdirSync(SCREENSHOTS_DIR, { recursive: true });
1042
1058
  }
1059
+ function ensureParentDir(filePath) {
1060
+ const dir = dirname(filePath);
1061
+ if (dir === "." || dir === "/") return null;
1062
+ try {
1063
+ mkdirSync(dir, { recursive: true });
1064
+ return null;
1065
+ } catch (err) {
1066
+ return err instanceof Error ? err.message : String(err);
1067
+ }
1068
+ }
1043
1069
  function generateScreenshotPath(format) {
1044
1070
  const timestamp = Date.now();
1045
1071
  const random = Math.random().toString(36).slice(2, 8);
@@ -1083,7 +1109,15 @@ var screenshotCommand = registerCommand({
1083
1109
  buffer = await ctx.page.screenshot(options);
1084
1110
  }
1085
1111
  if (p.output) {
1086
- writeFileSync(p.output, buffer, "binary");
1112
+ const dirErr = ensureParentDir(p.output);
1113
+ if (dirErr) {
1114
+ return fail3(`Cannot create directory for --output "${p.output}": ${dirErr}`);
1115
+ }
1116
+ try {
1117
+ writeFileSync(p.output, buffer, "binary");
1118
+ } catch (err) {
1119
+ return fail3(`Failed to write screenshot to "${p.output}": ${err instanceof Error ? err.message : String(err)}`);
1120
+ }
1087
1121
  return ok9({
1088
1122
  output: p.output,
1089
1123
  format,
@@ -1201,7 +1235,7 @@ var setViewportCommand = registerCommand({
1201
1235
 
1202
1236
  // src/commands/frame.ts
1203
1237
  import { z as z12 } from "zod";
1204
- import { ok as ok12, fail as fail2 } from "@dyyz1993/xcli-core";
1238
+ import { ok as ok12, fail as fail4 } from "@dyyz1993/xcli-core";
1205
1239
  var framesCommand = registerCommand({
1206
1240
  name: "frames",
1207
1241
  description: "List all frames in the current page",
@@ -1226,10 +1260,10 @@ var framesCommand = registerCommand({
1226
1260
  });
1227
1261
  var frameCommand = registerCommand({
1228
1262
  name: "frame",
1229
- description: "Switch to a frame by index or name",
1263
+ description: "Get frame info by index or name",
1230
1264
  scope: "page",
1231
1265
  parameters: z12.object({
1232
- index: z12.number().optional(),
1266
+ index: z12.number().int().min(0).optional(),
1233
1267
  name: z12.string().optional()
1234
1268
  }),
1235
1269
  result: z12.object({
@@ -1246,10 +1280,10 @@ var frameCommand = registerCommand({
1246
1280
  } else if (p.name !== void 0) {
1247
1281
  targetFrame = rawFrames.find((f) => f.name() === p.name);
1248
1282
  } else {
1249
- return fail2("Must provide index or name");
1283
+ return fail4("Must provide index or name");
1250
1284
  }
1251
1285
  if (!targetFrame) {
1252
- return fail2("Frame not found");
1286
+ return fail4("Frame not found");
1253
1287
  }
1254
1288
  return ok12({
1255
1289
  name: targetFrame.name(),
@@ -1895,7 +1929,7 @@ var actionsCommand = registerCommand({
1895
1929
 
1896
1930
  // src/commands/scrape.ts
1897
1931
  import { z as z15 } from "zod";
1898
- import { ok as ok15, fail as fail3 } from "@dyyz1993/xcli-core";
1932
+ import { ok as ok15, fail as fail5 } from "@dyyz1993/xcli-core";
1899
1933
 
1900
1934
  // src/lib/html-to-markdown.ts
1901
1935
  import * as cheerio from "cheerio";
@@ -2296,7 +2330,7 @@ var scrapeCommand = registerCommand({
2296
2330
  try {
2297
2331
  const targetUrl = p.url || page.url();
2298
2332
  if (!targetUrl || targetUrl === "about:blank") {
2299
- return fail3("URL is required. Provide a URL or connect to a browser (--cdp) with a non-blank page.");
2333
+ return fail5("URL is required. Provide a URL or connect to a browser (--cdp) with a non-blank page.");
2300
2334
  }
2301
2335
  let lastError;
2302
2336
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
@@ -2450,7 +2484,7 @@ var scrapeCommand = registerCommand({
2450
2484
  }
2451
2485
  }
2452
2486
  }
2453
- return fail3(`Scrape failed after ${maxAttempts} attempt(s): ${lastError?.message ?? "unknown error"}`);
2487
+ return fail5(`Scrape failed after ${maxAttempts} attempt(s): ${lastError?.message ?? "unknown error"}`);
2454
2488
  } finally {
2455
2489
  await closeEphemeralContext(context);
2456
2490
  }
@@ -2463,7 +2497,7 @@ var scrapeCommand = registerCommand({
2463
2497
 
2464
2498
  // src/commands/map.ts
2465
2499
  import { z as z16 } from "zod";
2466
- import { ok as ok16, fail as fail4 } from "@dyyz1993/xcli-core";
2500
+ import { ok as ok16, fail as fail6 } from "@dyyz1993/xcli-core";
2467
2501
 
2468
2502
  // src/utils/url.ts
2469
2503
  var SKIP_EXTENSIONS = /* @__PURE__ */ new Set([
@@ -2712,7 +2746,7 @@ var mapCommand = registerCommand({
2712
2746
  try {
2713
2747
  const targetUrl = p.url || page.url();
2714
2748
  if (!targetUrl || targetUrl === "about:blank") {
2715
- return fail4("URL is required. Provide a URL or connect to a browser (--cdp) with a non-blank page.");
2749
+ return fail6("URL is required. Provide a URL or connect to a browser (--cdp) with a non-blank page.");
2716
2750
  }
2717
2751
  const links = await discoverUrls(page, targetUrl, {
2718
2752
  sitemap: p.sitemap,
@@ -3557,7 +3591,7 @@ ${errors.map((e) => ` - ${e.engine}: ${e.error}`).join("\n")}`
3557
3591
 
3558
3592
  // src/commands/network.ts
3559
3593
  import { z as z19 } from "zod";
3560
- import { ok as ok19, fail as fail5 } from "@dyyz1993/xcli-core";
3594
+ import { ok as ok19, fail as fail7 } from "@dyyz1993/xcli-core";
3561
3595
  function extractPath2(url) {
3562
3596
  try {
3563
3597
  const u = new URL(url);
@@ -3722,7 +3756,7 @@ var networkCommand = registerCommand({
3722
3756
  };
3723
3757
  if (p.listen) {
3724
3758
  const page2 = ctx.page;
3725
- if (!page2) return fail5("No active page. Use --cdp to connect first.");
3759
+ if (!page2) return fail7("No active page. Use --cdp to connect first.");
3726
3760
  const captures = [];
3727
3761
  const consoleMessages = [];
3728
3762
  const wsCaptures = [];
@@ -4102,7 +4136,7 @@ var ENGINE_KEY_ENUM = z20.enum(ALL_ENGINE_KEYS);
4102
4136
 
4103
4137
  // src/commands/snapshot.ts
4104
4138
  import { z as z21 } from "zod";
4105
- import { ok as ok20, fail as fail6, normalizeTips as normalizeTips3 } from "@dyyz1993/xcli-core";
4139
+ import { ok as ok20, fail as fail8, normalizeTips as normalizeTips3 } from "@dyyz1993/xcli-core";
4106
4140
 
4107
4141
  // src/runtime/ref-store.ts
4108
4142
  var sessions = /* @__PURE__ */ new Map();
@@ -4344,7 +4378,7 @@ async function resolveRefParams(page, params, selectorKeys, cache, sessionId) {
4344
4378
 
4345
4379
  // src/utils/site-semantics.ts
4346
4380
  import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync, readFileSync } from "fs";
4347
- import { join as join4, dirname } from "path";
4381
+ import { join as join4, dirname as dirname2 } from "path";
4348
4382
  import { homedir as homedir4 } from "os";
4349
4383
  import { stringify, parse } from "yaml";
4350
4384
  import { execFile } from "child_process";
@@ -4437,7 +4471,7 @@ function extractDomain2(url) {
4437
4471
  }
4438
4472
  function saveSemantics(domain, pagePath, url, elements) {
4439
4473
  const filePath = getSemanticsPath(domain);
4440
- const dir = dirname(filePath);
4474
+ const dir = dirname2(filePath);
4441
4475
  let site;
4442
4476
  if (existsSync(filePath)) {
4443
4477
  try {
@@ -5073,7 +5107,7 @@ var snapshotCommand = registerCommand({
5073
5107
  persistSemantics(url, aria);
5074
5108
  return ok20({ url, title, aria, text, dom }, normalizeTips3(tips));
5075
5109
  }
5076
- return fail6(`Unknown snapshot type: ${p.type}`);
5110
+ return fail8(`Unknown snapshot type: ${p.type}`);
5077
5111
  }
5078
5112
  });
5079
5113
  function persistSemantics(url, aria) {
@@ -5242,7 +5276,7 @@ var waitForCommand = registerCommand({
5242
5276
 
5243
5277
  // src/commands/tab.ts
5244
5278
  import { z as z23 } from "zod";
5245
- import { ok as ok22, fail as fail7 } from "@dyyz1993/xcli-core";
5279
+ import { ok as ok22, fail as fail9 } from "@dyyz1993/xcli-core";
5246
5280
  var TabParams = z23.object({
5247
5281
  subcommand: z23.enum(["list", "new", "close", "switch"]),
5248
5282
  url: z23.string().optional(),
@@ -5259,7 +5293,7 @@ var tabCommand = registerCommand({
5259
5293
  }),
5260
5294
  handler: async (p, ctx) => {
5261
5295
  if (!ctx.browserContext) {
5262
- return fail7("No browser context available. Use --cdp to connect to a browser first.");
5296
+ return fail9("No browser context available. Use --cdp to connect to a browser first.");
5263
5297
  }
5264
5298
  const pages = ctx.browserContext.pages();
5265
5299
  switch (p.subcommand) {
@@ -5268,50 +5302,45 @@ var tabCommand = registerCommand({
5268
5302
  case "new":
5269
5303
  return handleNew(p, pages, ctx);
5270
5304
  case "close":
5271
- return handleClose(p, pages, ctx);
5305
+ return handleClose(p, ctx);
5272
5306
  case "switch":
5273
5307
  return handleSwitch(p, pages, ctx);
5274
5308
  default:
5275
- return fail7(`Unknown subcommand: ${p.subcommand}`);
5309
+ return fail9(`Unknown subcommand: ${p.subcommand}`);
5276
5310
  }
5277
5311
  }
5278
5312
  });
5279
- function handleList(pages, ctx) {
5280
- const currentIndex = pages.indexOf(ctx.page);
5281
- const tabs = pages.map((page, i) => {
5313
+ async function handleList(pages, ctx) {
5314
+ const tabs = [];
5315
+ let activeIndex = -1;
5316
+ for (let i = 0; i < pages.length; i++) {
5317
+ const page = pages[i];
5282
5318
  const url = page.url();
5283
- let title = "";
5284
- try {
5285
- const t = page.title();
5286
- if (t instanceof Promise) {
5287
- void t.then((v) => {
5288
- title = v;
5289
- });
5290
- }
5291
- } catch {
5292
- title = "";
5293
- }
5294
- return {
5295
- index: i,
5296
- url,
5297
- title,
5298
- active: i === currentIndex
5299
- };
5300
- });
5301
- return ok22({ tabs, total: tabs.length, activeIndex: currentIndex });
5319
+ const title = await page.title().catch(() => "");
5320
+ const isActive = page === ctx.page;
5321
+ if (isActive) activeIndex = i;
5322
+ tabs.push({ index: i, url, title, active: isActive });
5323
+ }
5324
+ return ok22({ tabs, total: tabs.length, activeIndex });
5302
5325
  }
5303
5326
  async function handleNew(p, _pages, ctx) {
5304
5327
  const newPage = await ctx.browserContext.newPage();
5328
+ const warnings = [];
5305
5329
  if (p.url) {
5306
5330
  let url = p.url;
5307
- if (!/^https?:\/\//i.test(url)) {
5331
+ if (!/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(url)) {
5308
5332
  url = "https://" + url;
5309
5333
  }
5310
- await newPage.goto(url, { waitUntil: "domcontentloaded", timeout: 15e3 }).catch(() => {
5311
- });
5334
+ try {
5335
+ await newPage.goto(url, { waitUntil: "domcontentloaded", timeout: 15e3 });
5336
+ } catch (err) {
5337
+ warnings.push(`Navigation to "${url}" failed: ${err instanceof Error ? err.message : String(err)}`);
5338
+ }
5339
+ }
5340
+ try {
5341
+ await newPage.waitForLoadState("domcontentloaded");
5342
+ } catch {
5312
5343
  }
5313
- await newPage.waitForLoadState("domcontentloaded").catch(() => {
5314
- });
5315
5344
  const session = ctx.sessionId ? getSessionById(ctx.sessionId) : void 0;
5316
5345
  if (session) {
5317
5346
  setActivePage(session, newPage);
@@ -5324,18 +5353,20 @@ async function handleNew(p, _pages, ctx) {
5324
5353
  index: newIndex >= 0 ? newIndex : allPages.length - 1,
5325
5354
  url: newPage.url(),
5326
5355
  title,
5327
- total: allPages.length
5356
+ total: allPages.length,
5357
+ ...warnings.length > 0 ? { warning: warnings.join("; ") } : {}
5328
5358
  });
5329
5359
  }
5330
- async function handleClose(p, pages, ctx) {
5331
- if (pages.length <= 1) {
5332
- return fail7("Cannot close the last remaining tab");
5360
+ async function handleClose(p, ctx) {
5361
+ const currentPages = ctx.browserContext.pages();
5362
+ if (currentPages.length <= 1) {
5363
+ return fail9("Cannot close the last remaining tab");
5333
5364
  }
5334
- const closeIndex = p.index ?? pages.indexOf(ctx.page);
5335
- if (closeIndex < 0 || closeIndex >= pages.length) {
5336
- return fail7(`Invalid tab index: ${closeIndex}. Valid range: 0-${pages.length - 1}`);
5365
+ const closeIndex = p.index ?? currentPages.findIndex((pg) => pg === ctx.page);
5366
+ if (closeIndex < 0 || closeIndex >= currentPages.length) {
5367
+ return fail9(`Invalid tab index: ${closeIndex}. Valid range: 0-${currentPages.length - 1}`);
5337
5368
  }
5338
- const pageToClose = pages[closeIndex];
5369
+ const pageToClose = currentPages[closeIndex];
5339
5370
  const isActivePage = pageToClose === ctx.page;
5340
5371
  await pageToClose.close();
5341
5372
  const remainingPages = ctx.browserContext.pages();
@@ -5351,15 +5382,15 @@ async function handleClose(p, pages, ctx) {
5351
5382
  return ok22({
5352
5383
  closedIndex: closeIndex,
5353
5384
  total: remainingPages.length,
5354
- activeIndex: isActivePage ? closeIndex < remainingPages.length ? closeIndex : remainingPages.length - 1 : pages.indexOf(ctx.page)
5385
+ activeIndex: isActivePage ? closeIndex < remainingPages.length ? closeIndex : remainingPages.length - 1 : remainingPages.findIndex((pg) => pg === ctx.page)
5355
5386
  });
5356
5387
  }
5357
5388
  async function handleSwitch(p, pages, ctx) {
5358
5389
  if (p.index === void 0) {
5359
- return fail7("Parameter --index is required for switch subcommand");
5390
+ return fail9("Parameter --index is required for switch subcommand");
5360
5391
  }
5361
5392
  if (p.index < 0 || p.index >= pages.length) {
5362
- return fail7(`Invalid tab index: ${p.index}. Valid range: 0-${pages.length - 1}`);
5393
+ return fail9(`Invalid tab index: ${p.index}. Valid range: 0-${pages.length - 1}`);
5363
5394
  }
5364
5395
  const targetPage = pages[p.index];
5365
5396
  await targetPage.bringToFront().catch(() => {
@@ -5611,7 +5642,7 @@ registerCommandDefinition("addinitscript", ["script"]);
5611
5642
 
5612
5643
  // src/commands/find.ts
5613
5644
  import { z as z25 } from "zod";
5614
- import { ok as ok24, fail as fail8, normalizeTips as normalizeTips5 } from "@dyyz1993/xcli-core";
5645
+ import { ok as ok24, fail as fail10, normalizeTips as normalizeTips5 } from "@dyyz1993/xcli-core";
5615
5646
  var actionSchema2 = z25.enum(["click", "fill", "type", "select", "hover", "check"]);
5616
5647
  var findCommand = registerCommand({
5617
5648
  name: "find",
@@ -5652,7 +5683,7 @@ var findCommand = registerCommand({
5652
5683
  });
5653
5684
  const count = await locator.count();
5654
5685
  if (count === 0) {
5655
- return fail8(`No element found with ${p.strategy}="${p.value}"`);
5686
+ return fail10(`No element found with ${p.strategy}="${p.value}"`);
5656
5687
  }
5657
5688
  const tips = [];
5658
5689
  const target = selectTarget(locator, p.strategy);
@@ -5664,15 +5695,15 @@ var findCommand = registerCommand({
5664
5695
  await target.click({ timeout: p.timeout, force: true });
5665
5696
  return okWithTips({ matched: count, selector, action: "click" }, tips);
5666
5697
  } else if (actionName === "fill") {
5667
- if (actionValue === void 0) return fail8("find fill requires a value");
5698
+ if (actionValue === void 0) return fail10("find fill requires a value");
5668
5699
  await target.fill(actionValue, { timeout: p.timeout, force: true });
5669
5700
  return okWithTips({ matched: count, selector, action: `fill("${actionValue}")` }, tips);
5670
5701
  } else if (actionName === "type") {
5671
- if (actionValue === void 0) return fail8("find type requires a value");
5702
+ if (actionValue === void 0) return fail10("find type requires a value");
5672
5703
  await target.type(actionValue, { delay: 10, timeout: p.timeout });
5673
5704
  return okWithTips({ matched: count, selector, action: `type("${actionValue}")` }, tips);
5674
5705
  } else if (actionName === "select") {
5675
- if (actionValue === void 0) return fail8("find select requires a value");
5706
+ if (actionValue === void 0) return fail10("find select requires a value");
5676
5707
  await target.selectOption(actionValue);
5677
5708
  return okWithTips({ matched: count, selector, action: `select("${actionValue}")` }, tips);
5678
5709
  } else if (actionName === "hover") {
@@ -6678,7 +6709,7 @@ async function guardCheck(commandName) {
6678
6709
  }
6679
6710
  }
6680
6711
  function errorResult(message) {
6681
- return { ...fail9(message), duration: 0 };
6712
+ return { ...fail11(message), duration: 0 };
6682
6713
  }
6683
6714
  function tipsToMessages(tips) {
6684
6715
  if (!tips || tips.length === 0) return [];
@@ -6979,7 +7010,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
6979
7010
  duration,
6980
7011
  timestamp: start
6981
7012
  });
6982
- return { ...fail9(errorMessage), duration };
7013
+ return { ...fail11(errorMessage), duration };
6983
7014
  } finally {
6984
7015
  }
6985
7016
  }
@@ -7020,7 +7051,7 @@ async function executeChain(input, options) {
7020
7051
  results.push({
7021
7052
  command: cmdName,
7022
7053
  raw: cmdStr,
7023
- ...fail9(`Plugin "${cmdName}" requires a sub-command`),
7054
+ ...fail11(`Plugin "${cmdName}" requires a sub-command`),
7024
7055
  duration: 0
7025
7056
  });
7026
7057
  if (type === "and") {
@@ -7039,7 +7070,7 @@ async function executeChain(input, options) {
7039
7070
  results.push({
7040
7071
  command: cmdName,
7041
7072
  raw: cmdStr,
7042
- ...fail9(`Unknown command "${subCommand}" for plugin "${cmdName}"`),
7073
+ ...fail11(`Unknown command "${subCommand}" for plugin "${cmdName}"`),
7043
7074
  duration: 0
7044
7075
  });
7045
7076
  if (type === "and") {
@@ -7171,7 +7202,7 @@ async function executeChain(input, options) {
7171
7202
  results.push({
7172
7203
  command: `${cmdName} ${subCommand}`,
7173
7204
  raw: cmdStr,
7174
- ...fail9(errorMessage),
7205
+ ...fail11(errorMessage),
7175
7206
  duration: duration2
7176
7207
  });
7177
7208
  if (type === "and") {
@@ -8009,9 +8040,9 @@ function createRPCHandler() {
8009
8040
  return result;
8010
8041
  }
8011
8042
  async function handlePluginsReload() {
8012
- const { resetPluginLoader } = await import("./plugin-singleton-SYJF6BD6.js");
8043
+ const { resetPluginLoader } = await import("./plugin-singleton-FCNDN3FX.js");
8013
8044
  resetPluginLoader();
8014
- const loader = await import("./plugin-singleton-SYJF6BD6.js").then((m) => m.getPluginLoader());
8045
+ const loader = await import("./plugin-singleton-FCNDN3FX.js").then((m) => m.getPluginLoader());
8015
8046
  const sites = loader.getCore().loader.getSites();
8016
8047
  return { ok: true, plugins: sites.length };
8017
8048
  }
@@ -8257,8 +8288,8 @@ function createRPCHandler() {
8257
8288
  activeRecorders.delete(sessionName);
8258
8289
  if (outputPath) {
8259
8290
  const { writeFileSync: writeFileSync6, mkdirSync: mkdirSync6 } = await import("fs");
8260
- const { dirname: dirname2 } = await import("path");
8261
- mkdirSync6(dirname2(outputPath), { recursive: true });
8291
+ const { dirname: dirname3 } = await import("path");
8292
+ mkdirSync6(dirname3(outputPath), { recursive: true });
8262
8293
  if (outputPath.endsWith(".yaml") || outputPath.endsWith(".yml")) {
8263
8294
  const yaml2 = (await import("yaml")).default;
8264
8295
  writeFileSync6(outputPath, yaml2.stringify(data), "utf-8");
@@ -8347,13 +8378,6 @@ function createRPCHandler() {
8347
8378
  if (!file) {
8348
8379
  return { ok: false, success: false, duration: 0, eventsPlayed: 0, totalEvents: 0, errors: [{ eventIndex: -1, error: "Missing file parameter" }] };
8349
8380
  }
8350
- const session = findSession(sessionName);
8351
- if (!session) {
8352
- return { ok: false, success: false, duration: 0, eventsPlayed: 0, totalEvents: 0, errors: [{ eventIndex: -1, error: "Session not found: " + sessionName }] };
8353
- }
8354
- if (!session.page) {
8355
- return { ok: false, success: false, duration: 0, eventsPlayed: 0, totalEvents: 0, errors: [{ eventIndex: -1, error: "Session has no page: " + sessionName }] };
8356
- }
8357
8381
  let rawContent;
8358
8382
  let parsed;
8359
8383
  try {
@@ -8364,8 +8388,19 @@ function createRPCHandler() {
8364
8388
  const yaml2 = (await import("yaml")).default;
8365
8389
  parsed = yaml2.parse(rawContent);
8366
8390
  }
8391
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
8392
+ return { ok: false, success: false, duration: 0, eventsPlayed: 0, totalEvents: 0, errors: [{ eventIndex: -1, error: `File "${file}" does not contain a valid recording. Expected a JSON/YAML object with an "actions" or "events" array, got ${parsed === null ? "empty content" : typeof parsed}.` }] };
8393
+ }
8367
8394
  } catch (e) {
8368
- return { ok: false, success: false, duration: 0, eventsPlayed: 0, totalEvents: 0, errors: [{ eventIndex: -1, error: "Failed to read/parse file: " + String(e) }] };
8395
+ const msg = e instanceof Error ? e.message : String(e);
8396
+ return { ok: false, success: false, duration: 0, eventsPlayed: 0, totalEvents: 0, errors: [{ eventIndex: -1, error: `Failed to parse "${file}" as JSON or YAML: ${msg.split("\n")[0]}` }] };
8397
+ }
8398
+ const session = findSession(sessionName);
8399
+ if (!session) {
8400
+ return { ok: false, success: false, duration: 0, eventsPlayed: 0, totalEvents: 0, errors: [{ eventIndex: -1, error: `Session not found: ${sessionName}. Open a browser first: xbrowser goto <url> --session ${sessionName}` }] };
8401
+ }
8402
+ if (!session.page) {
8403
+ return { ok: false, success: false, duration: 0, eventsPlayed: 0, totalEvents: 0, errors: [{ eventIndex: -1, error: "Session has no page: " + sessionName }] };
8369
8404
  }
8370
8405
  const isNewFormat = Array.isArray(parsed.actions);
8371
8406
  if (isNewFormat) {
@@ -2,7 +2,7 @@ import {
2
2
  extractAndSave,
3
3
  extractRecording,
4
4
  printExtractSummary
5
- } from "./chunk-MJFYLKGL.js";
5
+ } from "./chunk-X46HDAOT.js";
6
6
  import "./chunk-KFQGP6VL.js";
7
7
  export {
8
8
  extractAndSave,
@@ -4,9 +4,33 @@ import "./chunk-KFQGP6VL.js";
4
4
  import * as fs from "fs";
5
5
  import * as yaml from "yaml";
6
6
  function extractRecording(filePath) {
7
- const content = fs.readFileSync(filePath, "utf-8");
8
- const recording = yaml.parse(content);
9
- const events = recording.actions || recording.events || [];
7
+ let recording;
8
+ try {
9
+ const content = fs.readFileSync(filePath, "utf-8");
10
+ recording = yaml.parse(content);
11
+ } catch (e) {
12
+ throw new Error(`Failed to read "${filePath}": ${e instanceof Error ? e.message.split("\n")[0] : String(e)}`);
13
+ }
14
+ if (recording === null || typeof recording !== "object" || Array.isArray(recording)) {
15
+ throw new Error(`"${filePath}" does not contain a valid recording (expected a YAML object with events or actions).`);
16
+ }
17
+ let rawEvents = recording.events ?? [];
18
+ const rawActions = recording.actions;
19
+ if (Array.isArray(rawActions) && rawEvents.length === 0) {
20
+ rawEvents = rawActions.map((a) => {
21
+ const action = a;
22
+ const element = action.element ?? {};
23
+ return {
24
+ type: action.type,
25
+ selector: element.selector ?? action.selector,
26
+ tagName: element.tag ?? action.tagName,
27
+ data: { ...action.data, value: action.value, key: action.key },
28
+ timestamp: action.timestamp,
29
+ pageState: action.pageState
30
+ };
31
+ });
32
+ }
33
+ const events = rawEvents;
10
34
  const keyEvents = [];
11
35
  const eventTypes = {};
12
36
  for (const event of events) {
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  filterRecording,
3
3
  parseExcludeTypes
4
- } from "./chunk-GJAV3QGG.js";
4
+ } from "./chunk-ANVL2ID2.js";
5
5
  import "./chunk-KFQGP6VL.js";
6
6
  export {
7
7
  filterRecording,
@@ -24,9 +24,33 @@ var DEFAULT_EXCLUDE_TYPES = [
24
24
  "mousedown"
25
25
  ];
26
26
  function filterRecording(inputPath, outputPath, excludeTypes) {
27
- const content = fs.readFileSync(inputPath, "utf-8");
28
- const recording = yaml.parse(content);
29
- const events = recording.actions || recording.events || [];
27
+ let recording;
28
+ try {
29
+ const content = fs.readFileSync(inputPath, "utf-8");
30
+ recording = yaml.parse(content);
31
+ } catch (e) {
32
+ throw new Error(`Failed to read "${inputPath}": ${e instanceof Error ? e.message.split("\n")[0] : String(e)}`);
33
+ }
34
+ if (recording === null || typeof recording !== "object" || Array.isArray(recording)) {
35
+ throw new Error(`"${inputPath}" does not contain a valid recording (expected a YAML object with events or actions).`);
36
+ }
37
+ let rawEvents = recording.events ?? [];
38
+ const rawActions = recording.actions;
39
+ if (Array.isArray(rawActions) && rawEvents.length === 0) {
40
+ rawEvents = rawActions.map((a) => {
41
+ const action = a;
42
+ const element = action.element ?? {};
43
+ return {
44
+ type: action.type,
45
+ selector: element.selector ?? action.selector,
46
+ data: { ...action.data, value: action.value, key: action.key },
47
+ scrollX: action.scrollX,
48
+ scrollY: action.scrollY
49
+ };
50
+ });
51
+ delete recording.actions;
52
+ }
53
+ const events = rawEvents;
30
54
  const originalCount = events.length;
31
55
  const exclude = excludeTypes || DEFAULT_EXCLUDE_TYPES;
32
56
  const filteredEvents = events.filter((event) => {