@xbrowser/cli 1.8.7 → 1.9.1

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.
@@ -5,7 +5,7 @@ import {
5
5
  } from "./chunk-VEDJ5XSQ.js";
6
6
  import {
7
7
  SessionRecorder
8
- } from "./chunk-FL6DOSWV.js";
8
+ } from "./chunk-TTZNR3QP.js";
9
9
  import {
10
10
  closeEphemeralContext,
11
11
  closeSessionByName,
@@ -21,12 +21,12 @@ import {
21
21
  resolveLaunchOpts,
22
22
  saveSessionDiskMeta,
23
23
  setActivePage
24
- } from "./chunk-HXLMMSU3.js";
24
+ } from "./chunk-OOJ2H7IL.js";
25
25
  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,
@@ -38,15 +38,13 @@ import {
38
38
  import "./chunk-KFQGP6VL.js";
39
39
 
40
40
  // src/daemon/daemon-main.ts
41
- import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync6, appendFileSync, unlinkSync } from "fs";
42
- import { join as join8 } from "path";
43
- import { homedir as homedir8 } from "os";
41
+ import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, appendFileSync, unlinkSync } from "fs";
42
+ import { join as join7 } from "path";
43
+ import { homedir as homedir7 } from "os";
44
44
  import { startHttpServer } from "@dyyz1993/xcli-core";
45
45
 
46
46
  // src/daemon/rpc-handlers.ts
47
- import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, readFileSync as readFileSync5 } from "fs";
48
- import { join as join7 } from "path";
49
- import { homedir as homedir7 } from "os";
47
+ import { readFileSync as readFileSync5 } from "fs";
50
48
  import {
51
49
  createSessionMeta,
52
50
  removeSession
@@ -55,7 +53,7 @@ import {
55
53
  // src/executor.ts
56
54
  import {
57
55
  ok as ok25,
58
- fail as fail9,
56
+ fail as fail11,
59
57
  isCommandResult,
60
58
  CompositeStorage as CompositeStorage2,
61
59
  TipCollector as TipCollector2,
@@ -920,7 +918,7 @@ var evaluateCommand = registerCommand({
920
918
 
921
919
  // src/commands/storage.ts
922
920
  import { z as z8 } from "zod";
923
- import { ok as ok8 } from "@dyyz1993/xcli-core";
921
+ import { ok as ok8, fail as fail2 } from "@dyyz1993/xcli-core";
924
922
  var getCookiesCommand = registerCommand({
925
923
  name: "get-cookies",
926
924
  description: "Get all cookies for the current page",
@@ -942,6 +940,7 @@ var setCookieCommand = registerCommand({
942
940
  value: z8.coerce.string(),
943
941
  domain: z8.coerce.string().optional(),
944
942
  path: z8.coerce.string().optional(),
943
+ url: z8.string().optional().describe("Cookie URL (alternative to domain)"),
945
944
  expires: z8.number().optional(),
946
945
  httpOnly: z8.boolean().optional(),
947
946
  secure: z8.boolean().optional(),
@@ -961,6 +960,9 @@ var setCookieCommand = registerCommand({
961
960
  }
962
961
  }
963
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
+ }
964
966
  await ctx.browserContext.addCookies([cookie]);
965
967
  return ok8({ name: p.name });
966
968
  }
@@ -987,19 +989,23 @@ var getLocalStorageCommand = registerCommand({
987
989
  z8.object({ data: z8.record(z8.string()) })
988
990
  ]),
989
991
  handler: async (p, ctx) => {
990
- if (p.key) {
991
- const value = await ctx.page.evaluate((k) => localStorage.getItem(k), p.key);
992
- 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)}`);
993
1008
  }
994
- const data = await ctx.page.evaluate(() => {
995
- const entries = {};
996
- for (let i = 0; i < localStorage.length; i++) {
997
- const key = localStorage.key(i);
998
- if (key) entries[key] = localStorage.getItem(key) ?? "";
999
- }
1000
- return entries;
1001
- });
1002
- return ok8({ data });
1003
1009
  }
1004
1010
  });
1005
1011
  var setLocalStorageCommand = registerCommand({
@@ -1012,13 +1018,17 @@ var setLocalStorageCommand = registerCommand({
1012
1018
  }),
1013
1019
  result: z8.object({ key: z8.string() }),
1014
1020
  handler: async (p, ctx) => {
1015
- await ctx.page.evaluate(
1016
- (args) => {
1017
- localStorage.setItem(args.key, args.value);
1018
- },
1019
- { key: p.key, value: p.value }
1020
- );
1021
- 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
+ }
1022
1032
  }
1023
1033
  });
1024
1034
  var clearLocalStorageCommand = registerCommand({
@@ -1027,21 +1037,35 @@ var clearLocalStorageCommand = registerCommand({
1027
1037
  scope: "page",
1028
1038
  result: z8.object({ cleared: z8.boolean() }),
1029
1039
  handler: async (_p, ctx) => {
1030
- await ctx.page.evaluate(() => localStorage.clear());
1031
- 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
+ }
1032
1046
  }
1033
1047
  });
1034
1048
 
1035
1049
  // src/commands/screenshot.ts
1036
1050
  import { z as z9 } from "zod";
1037
- import { ok as ok9 } from "@dyyz1993/xcli-core";
1051
+ import { ok as ok9, fail as fail3 } from "@dyyz1993/xcli-core";
1038
1052
  import { writeFileSync, mkdirSync } from "fs";
1039
- import { join as join2 } from "path";
1053
+ import { dirname, join as join2 } from "path";
1040
1054
  import { homedir as homedir2 } from "os";
1041
1055
  var SCREENSHOTS_DIR = join2(homedir2(), ".xbrowser", "screenshots");
1042
1056
  function ensureScreenshotsDir() {
1043
1057
  mkdirSync(SCREENSHOTS_DIR, { recursive: true });
1044
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
+ }
1045
1069
  function generateScreenshotPath(format) {
1046
1070
  const timestamp = Date.now();
1047
1071
  const random = Math.random().toString(36).slice(2, 8);
@@ -1085,7 +1109,15 @@ var screenshotCommand = registerCommand({
1085
1109
  buffer = await ctx.page.screenshot(options);
1086
1110
  }
1087
1111
  if (p.output) {
1088
- 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
+ }
1089
1121
  return ok9({
1090
1122
  output: p.output,
1091
1123
  format,
@@ -1203,7 +1235,7 @@ var setViewportCommand = registerCommand({
1203
1235
 
1204
1236
  // src/commands/frame.ts
1205
1237
  import { z as z12 } from "zod";
1206
- import { ok as ok12, fail as fail2 } from "@dyyz1993/xcli-core";
1238
+ import { ok as ok12, fail as fail4 } from "@dyyz1993/xcli-core";
1207
1239
  var framesCommand = registerCommand({
1208
1240
  name: "frames",
1209
1241
  description: "List all frames in the current page",
@@ -1228,10 +1260,10 @@ var framesCommand = registerCommand({
1228
1260
  });
1229
1261
  var frameCommand = registerCommand({
1230
1262
  name: "frame",
1231
- description: "Switch to a frame by index or name",
1263
+ description: "Get frame info by index or name",
1232
1264
  scope: "page",
1233
1265
  parameters: z12.object({
1234
- index: z12.number().optional(),
1266
+ index: z12.number().int().min(0).optional(),
1235
1267
  name: z12.string().optional()
1236
1268
  }),
1237
1269
  result: z12.object({
@@ -1248,10 +1280,10 @@ var frameCommand = registerCommand({
1248
1280
  } else if (p.name !== void 0) {
1249
1281
  targetFrame = rawFrames.find((f) => f.name() === p.name);
1250
1282
  } else {
1251
- return fail2("Must provide index or name");
1283
+ return fail4("Must provide index or name");
1252
1284
  }
1253
1285
  if (!targetFrame) {
1254
- return fail2("Frame not found");
1286
+ return fail4("Frame not found");
1255
1287
  }
1256
1288
  return ok12({
1257
1289
  name: targetFrame.name(),
@@ -1897,7 +1929,7 @@ var actionsCommand = registerCommand({
1897
1929
 
1898
1930
  // src/commands/scrape.ts
1899
1931
  import { z as z15 } from "zod";
1900
- import { ok as ok15, fail as fail3 } from "@dyyz1993/xcli-core";
1932
+ import { ok as ok15, fail as fail5 } from "@dyyz1993/xcli-core";
1901
1933
 
1902
1934
  // src/lib/html-to-markdown.ts
1903
1935
  import * as cheerio from "cheerio";
@@ -2298,7 +2330,7 @@ var scrapeCommand = registerCommand({
2298
2330
  try {
2299
2331
  const targetUrl = p.url || page.url();
2300
2332
  if (!targetUrl || targetUrl === "about:blank") {
2301
- 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.");
2302
2334
  }
2303
2335
  let lastError;
2304
2336
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
@@ -2452,7 +2484,7 @@ var scrapeCommand = registerCommand({
2452
2484
  }
2453
2485
  }
2454
2486
  }
2455
- 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"}`);
2456
2488
  } finally {
2457
2489
  await closeEphemeralContext(context);
2458
2490
  }
@@ -2465,7 +2497,7 @@ var scrapeCommand = registerCommand({
2465
2497
 
2466
2498
  // src/commands/map.ts
2467
2499
  import { z as z16 } from "zod";
2468
- import { ok as ok16, fail as fail4 } from "@dyyz1993/xcli-core";
2500
+ import { ok as ok16, fail as fail6 } from "@dyyz1993/xcli-core";
2469
2501
 
2470
2502
  // src/utils/url.ts
2471
2503
  var SKIP_EXTENSIONS = /* @__PURE__ */ new Set([
@@ -2714,7 +2746,7 @@ var mapCommand = registerCommand({
2714
2746
  try {
2715
2747
  const targetUrl = p.url || page.url();
2716
2748
  if (!targetUrl || targetUrl === "about:blank") {
2717
- 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.");
2718
2750
  }
2719
2751
  const links = await discoverUrls(page, targetUrl, {
2720
2752
  sitemap: p.sitemap,
@@ -3559,7 +3591,7 @@ ${errors.map((e) => ` - ${e.engine}: ${e.error}`).join("\n")}`
3559
3591
 
3560
3592
  // src/commands/network.ts
3561
3593
  import { z as z19 } from "zod";
3562
- import { ok as ok19, fail as fail5 } from "@dyyz1993/xcli-core";
3594
+ import { ok as ok19, fail as fail7 } from "@dyyz1993/xcli-core";
3563
3595
  function extractPath2(url) {
3564
3596
  try {
3565
3597
  const u = new URL(url);
@@ -3724,7 +3756,7 @@ var networkCommand = registerCommand({
3724
3756
  };
3725
3757
  if (p.listen) {
3726
3758
  const page2 = ctx.page;
3727
- 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.");
3728
3760
  const captures = [];
3729
3761
  const consoleMessages = [];
3730
3762
  const wsCaptures = [];
@@ -4104,7 +4136,7 @@ var ENGINE_KEY_ENUM = z20.enum(ALL_ENGINE_KEYS);
4104
4136
 
4105
4137
  // src/commands/snapshot.ts
4106
4138
  import { z as z21 } from "zod";
4107
- 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";
4108
4140
 
4109
4141
  // src/runtime/ref-store.ts
4110
4142
  var sessions = /* @__PURE__ */ new Map();
@@ -4346,7 +4378,7 @@ async function resolveRefParams(page, params, selectorKeys, cache, sessionId) {
4346
4378
 
4347
4379
  // src/utils/site-semantics.ts
4348
4380
  import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync, readFileSync } from "fs";
4349
- import { join as join4, dirname } from "path";
4381
+ import { join as join4, dirname as dirname2 } from "path";
4350
4382
  import { homedir as homedir4 } from "os";
4351
4383
  import { stringify, parse } from "yaml";
4352
4384
  import { execFile } from "child_process";
@@ -4439,7 +4471,7 @@ function extractDomain2(url) {
4439
4471
  }
4440
4472
  function saveSemantics(domain, pagePath, url, elements) {
4441
4473
  const filePath = getSemanticsPath(domain);
4442
- const dir = dirname(filePath);
4474
+ const dir = dirname2(filePath);
4443
4475
  let site;
4444
4476
  if (existsSync(filePath)) {
4445
4477
  try {
@@ -5075,7 +5107,7 @@ var snapshotCommand = registerCommand({
5075
5107
  persistSemantics(url, aria);
5076
5108
  return ok20({ url, title, aria, text, dom }, normalizeTips3(tips));
5077
5109
  }
5078
- return fail6(`Unknown snapshot type: ${p.type}`);
5110
+ return fail8(`Unknown snapshot type: ${p.type}`);
5079
5111
  }
5080
5112
  });
5081
5113
  function persistSemantics(url, aria) {
@@ -5244,7 +5276,7 @@ var waitForCommand = registerCommand({
5244
5276
 
5245
5277
  // src/commands/tab.ts
5246
5278
  import { z as z23 } from "zod";
5247
- import { ok as ok22, fail as fail7 } from "@dyyz1993/xcli-core";
5279
+ import { ok as ok22, fail as fail9 } from "@dyyz1993/xcli-core";
5248
5280
  var TabParams = z23.object({
5249
5281
  subcommand: z23.enum(["list", "new", "close", "switch"]),
5250
5282
  url: z23.string().optional(),
@@ -5261,7 +5293,7 @@ var tabCommand = registerCommand({
5261
5293
  }),
5262
5294
  handler: async (p, ctx) => {
5263
5295
  if (!ctx.browserContext) {
5264
- 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.");
5265
5297
  }
5266
5298
  const pages = ctx.browserContext.pages();
5267
5299
  switch (p.subcommand) {
@@ -5270,50 +5302,45 @@ var tabCommand = registerCommand({
5270
5302
  case "new":
5271
5303
  return handleNew(p, pages, ctx);
5272
5304
  case "close":
5273
- return handleClose(p, pages, ctx);
5305
+ return handleClose(p, ctx);
5274
5306
  case "switch":
5275
5307
  return handleSwitch(p, pages, ctx);
5276
5308
  default:
5277
- return fail7(`Unknown subcommand: ${p.subcommand}`);
5309
+ return fail9(`Unknown subcommand: ${p.subcommand}`);
5278
5310
  }
5279
5311
  }
5280
5312
  });
5281
- function handleList(pages, ctx) {
5282
- const currentIndex = pages.indexOf(ctx.page);
5283
- 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];
5284
5318
  const url = page.url();
5285
- let title = "";
5286
- try {
5287
- const t = page.title();
5288
- if (t instanceof Promise) {
5289
- void t.then((v) => {
5290
- title = v;
5291
- });
5292
- }
5293
- } catch {
5294
- title = "";
5295
- }
5296
- return {
5297
- index: i,
5298
- url,
5299
- title,
5300
- active: i === currentIndex
5301
- };
5302
- });
5303
- 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 });
5304
5325
  }
5305
5326
  async function handleNew(p, _pages, ctx) {
5306
5327
  const newPage = await ctx.browserContext.newPage();
5328
+ const warnings = [];
5307
5329
  if (p.url) {
5308
5330
  let url = p.url;
5309
- if (!/^https?:\/\//i.test(url)) {
5331
+ if (!/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(url)) {
5310
5332
  url = "https://" + url;
5311
5333
  }
5312
- await newPage.goto(url, { waitUntil: "domcontentloaded", timeout: 15e3 }).catch(() => {
5313
- });
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 {
5314
5343
  }
5315
- await newPage.waitForLoadState("domcontentloaded").catch(() => {
5316
- });
5317
5344
  const session = ctx.sessionId ? getSessionById(ctx.sessionId) : void 0;
5318
5345
  if (session) {
5319
5346
  setActivePage(session, newPage);
@@ -5326,18 +5353,20 @@ async function handleNew(p, _pages, ctx) {
5326
5353
  index: newIndex >= 0 ? newIndex : allPages.length - 1,
5327
5354
  url: newPage.url(),
5328
5355
  title,
5329
- total: allPages.length
5356
+ total: allPages.length,
5357
+ ...warnings.length > 0 ? { warning: warnings.join("; ") } : {}
5330
5358
  });
5331
5359
  }
5332
- async function handleClose(p, pages, ctx) {
5333
- if (pages.length <= 1) {
5334
- 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");
5335
5364
  }
5336
- const closeIndex = p.index ?? pages.indexOf(ctx.page);
5337
- if (closeIndex < 0 || closeIndex >= pages.length) {
5338
- 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}`);
5339
5368
  }
5340
- const pageToClose = pages[closeIndex];
5369
+ const pageToClose = currentPages[closeIndex];
5341
5370
  const isActivePage = pageToClose === ctx.page;
5342
5371
  await pageToClose.close();
5343
5372
  const remainingPages = ctx.browserContext.pages();
@@ -5353,15 +5382,15 @@ async function handleClose(p, pages, ctx) {
5353
5382
  return ok22({
5354
5383
  closedIndex: closeIndex,
5355
5384
  total: remainingPages.length,
5356
- 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)
5357
5386
  });
5358
5387
  }
5359
5388
  async function handleSwitch(p, pages, ctx) {
5360
5389
  if (p.index === void 0) {
5361
- return fail7("Parameter --index is required for switch subcommand");
5390
+ return fail9("Parameter --index is required for switch subcommand");
5362
5391
  }
5363
5392
  if (p.index < 0 || p.index >= pages.length) {
5364
- 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}`);
5365
5394
  }
5366
5395
  const targetPage = pages[p.index];
5367
5396
  await targetPage.bringToFront().catch(() => {
@@ -5613,7 +5642,7 @@ registerCommandDefinition("addinitscript", ["script"]);
5613
5642
 
5614
5643
  // src/commands/find.ts
5615
5644
  import { z as z25 } from "zod";
5616
- 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";
5617
5646
  var actionSchema2 = z25.enum(["click", "fill", "type", "select", "hover", "check"]);
5618
5647
  var findCommand = registerCommand({
5619
5648
  name: "find",
@@ -5654,7 +5683,7 @@ var findCommand = registerCommand({
5654
5683
  });
5655
5684
  const count = await locator.count();
5656
5685
  if (count === 0) {
5657
- return fail8(`No element found with ${p.strategy}="${p.value}"`);
5686
+ return fail10(`No element found with ${p.strategy}="${p.value}"`);
5658
5687
  }
5659
5688
  const tips = [];
5660
5689
  const target = selectTarget(locator, p.strategy);
@@ -5666,15 +5695,15 @@ var findCommand = registerCommand({
5666
5695
  await target.click({ timeout: p.timeout, force: true });
5667
5696
  return okWithTips({ matched: count, selector, action: "click" }, tips);
5668
5697
  } else if (actionName === "fill") {
5669
- if (actionValue === void 0) return fail8("find fill requires a value");
5698
+ if (actionValue === void 0) return fail10("find fill requires a value");
5670
5699
  await target.fill(actionValue, { timeout: p.timeout, force: true });
5671
5700
  return okWithTips({ matched: count, selector, action: `fill("${actionValue}")` }, tips);
5672
5701
  } else if (actionName === "type") {
5673
- if (actionValue === void 0) return fail8("find type requires a value");
5702
+ if (actionValue === void 0) return fail10("find type requires a value");
5674
5703
  await target.type(actionValue, { delay: 10, timeout: p.timeout });
5675
5704
  return okWithTips({ matched: count, selector, action: `type("${actionValue}")` }, tips);
5676
5705
  } else if (actionName === "select") {
5677
- if (actionValue === void 0) return fail8("find select requires a value");
5706
+ if (actionValue === void 0) return fail10("find select requires a value");
5678
5707
  await target.selectOption(actionValue);
5679
5708
  return okWithTips({ matched: count, selector, action: `select("${actionValue}")` }, tips);
5680
5709
  } else if (actionName === "hover") {
@@ -6680,7 +6709,7 @@ async function guardCheck(commandName) {
6680
6709
  }
6681
6710
  }
6682
6711
  function errorResult(message) {
6683
- return { ...fail9(message), duration: 0 };
6712
+ return { ...fail11(message), duration: 0 };
6684
6713
  }
6685
6714
  function tipsToMessages(tips) {
6686
6715
  if (!tips || tips.length === 0) return [];
@@ -6757,7 +6786,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
6757
6786
  }
6758
6787
  let targetPageOverride = null;
6759
6788
  if (_target && extraOpts?.cdpEndpoint) {
6760
- const { findTargetPage } = await import("./browser-LW2MDJE4.js");
6789
+ const { findTargetPage } = await import("./browser-6WJHQDPV.js");
6761
6790
  targetPageOverride = await findTargetPage(extraOpts.cdpEndpoint, _target);
6762
6791
  if (!targetPageOverride) {
6763
6792
  return errorResult(`Target "${_target}" not found. Use 'xbrowser targets --cdp ${extraOpts.cdpEndpoint}' to list available pages.`);
@@ -6981,7 +7010,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
6981
7010
  duration,
6982
7011
  timestamp: start
6983
7012
  });
6984
- return { ...fail9(errorMessage), duration };
7013
+ return { ...fail11(errorMessage), duration };
6985
7014
  } finally {
6986
7015
  }
6987
7016
  }
@@ -7022,7 +7051,7 @@ async function executeChain(input, options) {
7022
7051
  results.push({
7023
7052
  command: cmdName,
7024
7053
  raw: cmdStr,
7025
- ...fail9(`Plugin "${cmdName}" requires a sub-command`),
7054
+ ...fail11(`Plugin "${cmdName}" requires a sub-command`),
7026
7055
  duration: 0
7027
7056
  });
7028
7057
  if (type === "and") {
@@ -7041,7 +7070,7 @@ async function executeChain(input, options) {
7041
7070
  results.push({
7042
7071
  command: cmdName,
7043
7072
  raw: cmdStr,
7044
- ...fail9(`Unknown command "${subCommand}" for plugin "${cmdName}"`),
7073
+ ...fail11(`Unknown command "${subCommand}" for plugin "${cmdName}"`),
7045
7074
  duration: 0
7046
7075
  });
7047
7076
  if (type === "and") {
@@ -7173,7 +7202,7 @@ async function executeChain(input, options) {
7173
7202
  results.push({
7174
7203
  command: `${cmdName} ${subCommand}`,
7175
7204
  raw: cmdStr,
7176
- ...fail9(errorMessage),
7205
+ ...fail11(errorMessage),
7177
7206
  duration: duration2
7178
7207
  });
7179
7208
  if (type === "and") {
@@ -7795,60 +7824,6 @@ var PlaybackEngine = class _PlaybackEngine {
7795
7824
  // src/daemon/rpc-handlers.ts
7796
7825
  var activeRecorders = /* @__PURE__ */ new Map();
7797
7826
  var replayResumeResolvers = /* @__PURE__ */ new Map();
7798
- var CONFIG_DIR3 = join7(homedir7(), ".xbrowser");
7799
- var RECORDING_INJECT_JS = `
7800
- (function(){
7801
- if(window.__xb_rec) return;
7802
- window.__xb_rec = true;
7803
- window.__xb_evts = [];
7804
- window.__xb_t0 = Date.now();
7805
- function d(el){
7806
- if(!el||!el.tagName) return {tag:'unknown'};
7807
- var o={tag:el.tagName.toLowerCase(),text:(el.textContent||'').trim().substring(0,80)};
7808
- if(el.getAttribute('role')) o.role=el.getAttribute('role');
7809
- if(el.id) o.id=el.id;
7810
- if(el.getAttribute('type')) o.type=el.getAttribute('type');
7811
- if(el.getAttribute('placeholder')) o.placeholder=el.getAttribute('placeholder');
7812
- if(el.getAttribute('aria-label')) o.ariaLabel=el.getAttribute('aria-label');
7813
- if(el.contentEditable==='true') o.contentEditable=true;
7814
- return o;
7815
- }
7816
- function p(t,det){
7817
- var e={type:t,ts:Date.now()-window.__xb_t0,url:location.href};
7818
- for(var k in det) e[k]=det[k];
7819
- window.__xb_evts.push(e);
7820
- }
7821
- document.addEventListener('click',function(e){p('click',{target:d(e.target),x:e.clientX,y:e.clientY})},true);
7822
- document.addEventListener('dblclick',function(e){p('dblclick',{target:d(e.target),x:e.clientX,y:e.clientY})},true);
7823
- document.addEventListener('input',function(e){var el=e.target;p('input',{target:d(el),value:(el.value||el.textContent||'').substring(0,200)})},true);
7824
- document.addEventListener('change',function(e){p('change',{target:d(e.target),value:(e.target.value||'').substring(0,100)})},true);
7825
- document.addEventListener('keydown',function(e){if(e.key==='Enter'||e.key==='Tab'||e.key==='Escape'||e.key.startsWith('Arrow'))p('keydown',{key:e.key,target:d(e.target)})},true);
7826
- document.addEventListener('submit',function(e){p('submit',{target:d(e.target)})},true);
7827
- var __xb_last_focus=null;document.addEventListener('focus',function(e){var t=e.target.tagName;if(t==='INPUT'||t==='TEXTAREA'||e.target.contentEditable==='true'){var sel=e.target.id||e.target.name||e.target.placeholder;if(sel===__xb_last_focus)return;__xb_last_focus=sel;p('input_focused',{target:d(e.target)})}},true);
7828
- var obs=new MutationObserver(function(mutations){
7829
- for(var m of mutations){
7830
- for(var node of m.addedNodes){
7831
- if(node.nodeType===1&&node.tagName){
7832
- var text=(node.textContent||'').trim().substring(0,60);
7833
- if(text&&text.length>1) p('dom_added',{tag:node.tagName.toLowerCase(),role:node.getAttribute&&node.getAttribute('role'),text:text});
7834
- }
7835
- }
7836
- }
7837
- });
7838
- if(document.body) obs.observe(document.body,{childList:true,subtree:true});
7839
- p('recording_started',{url:location.href});
7840
- })();
7841
- `;
7842
- async function injectRecording(page) {
7843
- try {
7844
- await page.evaluate(RECORDING_INJECT_JS);
7845
- } catch {
7846
- }
7847
- try {
7848
- await page.addInitScript(RECORDING_INJECT_JS);
7849
- } catch {
7850
- }
7851
- }
7852
7827
  function createRPCHandler() {
7853
7828
  let previewWS = null;
7854
7829
  const INTERACTION_COMMANDS2 = /* @__PURE__ */ new Set([
@@ -7913,15 +7888,6 @@ function createRPCHandler() {
7913
7888
  return handleNetworkFeedback(params);
7914
7889
  case "network:export":
7915
7890
  return handleNetworkExport(params);
7916
- // ── Recording ──
7917
- case "recording:status":
7918
- return handleRecordingStatus(params);
7919
- case "recording:events":
7920
- return handleRecordingEvents(params);
7921
- case "recording:clear":
7922
- return handleRecordingClear(params);
7923
- case "recording:save":
7924
- return handleRecordingSave(params);
7925
7891
  // ── Command log ──
7926
7892
  case "command:log":
7927
7893
  return handleCommandLog(params);
@@ -7988,7 +7954,6 @@ function createRPCHandler() {
7988
7954
  session = await createSession(name, url);
7989
7955
  }
7990
7956
  }
7991
- await injectRecording(session.page);
7992
7957
  if (previewWS) previewWS.registerSession(session.name, session.page);
7993
7958
  saveSessionDiskMeta(name, {
7994
7959
  id: session.id,
@@ -8075,9 +8040,9 @@ function createRPCHandler() {
8075
8040
  return result;
8076
8041
  }
8077
8042
  async function handlePluginsReload() {
8078
- const { resetPluginLoader } = await import("./plugin-singleton-SYJF6BD6.js");
8043
+ const { resetPluginLoader } = await import("./plugin-singleton-FCNDN3FX.js");
8079
8044
  resetPluginLoader();
8080
- 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());
8081
8046
  const sites = loader.getCore().loader.getSites();
8082
8047
  return { ok: true, plugins: sites.length };
8083
8048
  }
@@ -8209,61 +8174,6 @@ function createRPCHandler() {
8209
8174
  if (!entry.capture) return { error: `Entry #${id} not found` };
8210
8175
  return exportEntry(entry.capture, lang);
8211
8176
  }
8212
- async function handleRecordingStatus(params) {
8213
- const sess = findSession(params.session || "default");
8214
- if (!sess) return { recording: false, error: "No session" };
8215
- try {
8216
- const result = await sess.page.evaluate(() => ({
8217
- active: !!window.__xb_rec,
8218
- events: window.__xb_evts?.length || 0,
8219
- url: location.href
8220
- }));
8221
- return { recording: true, ...result };
8222
- } catch {
8223
- return { recording: false, error: "Page unreachable" };
8224
- }
8225
- }
8226
- async function handleRecordingEvents(params) {
8227
- const sess = findSession(params.session || "default");
8228
- if (!sess) return { events: [], error: "No session" };
8229
- try {
8230
- const events = await sess.page.evaluate(() => window.__xb_evts || []);
8231
- return { events, url: sess.page.url() };
8232
- } catch {
8233
- return { events: [], error: "Page unreachable" };
8234
- }
8235
- }
8236
- async function handleRecordingClear(params) {
8237
- const sess = findSession(params.session || "default");
8238
- if (!sess) return { ok: false, error: "No session" };
8239
- try {
8240
- await sess.page.evaluate(() => {
8241
- window.__xb_evts = [];
8242
- window.__xb_t0 = Date.now();
8243
- });
8244
- return { ok: true };
8245
- } catch {
8246
- return { ok: false, error: "Page unreachable" };
8247
- }
8248
- }
8249
- async function handleRecordingSave(params) {
8250
- const sess = findSession(params.session || "default");
8251
- if (!sess) return { ok: false, error: "No session" };
8252
- try {
8253
- const events = await sess.page.evaluate(() => window.__xb_evts || []);
8254
- const recordingsDir = join7(CONFIG_DIR3, "recordings");
8255
- mkdirSync5(recordingsDir, { recursive: true });
8256
- const outPath = params.path || join7(recordingsDir, `recording-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.json`);
8257
- writeFileSync5(outPath, JSON.stringify({
8258
- startUrl: sess.page.url(),
8259
- recordedAt: (/* @__PURE__ */ new Date()).toISOString(),
8260
- events
8261
- }, null, 2));
8262
- return { ok: true, path: outPath, events: events.length };
8263
- } catch (e) {
8264
- return { ok: false, error: errMsg(e) };
8265
- }
8266
- }
8267
8177
  function handleCommandLog(params) {
8268
8178
  const sessionName = params.session || "default";
8269
8179
  const limit = params.limit || 50;
@@ -8301,7 +8211,7 @@ function createRPCHandler() {
8301
8211
  } catch {
8302
8212
  }
8303
8213
  }
8304
- recorder.recordCommandAction({
8214
+ await recorder.recordCommandAction({
8305
8215
  type: actionType,
8306
8216
  selector,
8307
8217
  value,
@@ -8324,7 +8234,6 @@ function createRPCHandler() {
8324
8234
  try {
8325
8235
  const sessionOpts = cdpEndpoint ? { cdpEndpoint } : void 0;
8326
8236
  session = await createSession(sessionName, url, sessionOpts);
8327
- await injectRecording(session.page);
8328
8237
  if (previewWS) previewWS.registerSession(session.name, session.page);
8329
8238
  saveSessionDiskMeta(sessionName, {
8330
8239
  id: session.id,
@@ -8360,8 +8269,8 @@ function createRPCHandler() {
8360
8269
  const existingData = SessionRecorder.readData(sessionName);
8361
8270
  if (existingData) {
8362
8271
  if (outputPath) {
8363
- const { writeFileSync: writeFileSync7 } = await import("fs");
8364
- writeFileSync7(outputPath, JSON.stringify(existingData, null, 2), "utf-8");
8272
+ const { writeFileSync: writeFileSync6 } = await import("fs");
8273
+ writeFileSync6(outputPath, JSON.stringify(existingData, null, 2), "utf-8");
8365
8274
  }
8366
8275
  return {
8367
8276
  ok: true,
@@ -8378,14 +8287,14 @@ function createRPCHandler() {
8378
8287
  const { data, summary } = await recorder.stop();
8379
8288
  activeRecorders.delete(sessionName);
8380
8289
  if (outputPath) {
8381
- const { writeFileSync: writeFileSync7, mkdirSync: mkdirSync7 } = await import("fs");
8382
- const { dirname: dirname2 } = await import("path");
8383
- mkdirSync7(dirname2(outputPath), { recursive: true });
8290
+ const { writeFileSync: writeFileSync6, mkdirSync: mkdirSync6 } = await import("fs");
8291
+ const { dirname: dirname3 } = await import("path");
8292
+ mkdirSync6(dirname3(outputPath), { recursive: true });
8384
8293
  if (outputPath.endsWith(".yaml") || outputPath.endsWith(".yml")) {
8385
8294
  const yaml2 = (await import("yaml")).default;
8386
- writeFileSync7(outputPath, yaml2.stringify(data), "utf-8");
8295
+ writeFileSync6(outputPath, yaml2.stringify(data), "utf-8");
8387
8296
  } else {
8388
- writeFileSync7(outputPath, JSON.stringify(data, null, 2), "utf-8");
8297
+ writeFileSync6(outputPath, JSON.stringify(data, null, 2), "utf-8");
8389
8298
  }
8390
8299
  }
8391
8300
  return {
@@ -8469,13 +8378,6 @@ function createRPCHandler() {
8469
8378
  if (!file) {
8470
8379
  return { ok: false, success: false, duration: 0, eventsPlayed: 0, totalEvents: 0, errors: [{ eventIndex: -1, error: "Missing file parameter" }] };
8471
8380
  }
8472
- const session = findSession(sessionName);
8473
- if (!session) {
8474
- return { ok: false, success: false, duration: 0, eventsPlayed: 0, totalEvents: 0, errors: [{ eventIndex: -1, error: "Session not found: " + sessionName }] };
8475
- }
8476
- if (!session.page) {
8477
- return { ok: false, success: false, duration: 0, eventsPlayed: 0, totalEvents: 0, errors: [{ eventIndex: -1, error: "Session has no page: " + sessionName }] };
8478
- }
8479
8381
  let rawContent;
8480
8382
  let parsed;
8481
8383
  try {
@@ -8486,8 +8388,19 @@ function createRPCHandler() {
8486
8388
  const yaml2 = (await import("yaml")).default;
8487
8389
  parsed = yaml2.parse(rawContent);
8488
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
+ }
8489
8394
  } catch (e) {
8490
- 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 }] };
8491
8404
  }
8492
8405
  const isNewFormat = Array.isArray(parsed.actions);
8493
8406
  if (isNewFormat) {
@@ -9594,12 +9507,12 @@ var FileListHandler = class {
9594
9507
  const msg = ctx.message;
9595
9508
  try {
9596
9509
  const { readdirSync, statSync } = await import("fs");
9597
- const { join: join9, resolve } = await import("path");
9510
+ const { join: join8, resolve } = await import("path");
9598
9511
  const targetPath = resolve(msg.path);
9599
9512
  const entries = readdirSync(targetPath);
9600
9513
  const files = entries.map((name) => {
9601
9514
  try {
9602
- const stat = statSync(join9(targetPath, name));
9515
+ const stat = statSync(join8(targetPath, name));
9603
9516
  return { name, isDir: stat.isDirectory(), size: stat.size, modified: stat.mtime.toISOString() };
9604
9517
  } catch {
9605
9518
  return { name, isDir: false, size: 0, modified: "" };
@@ -11260,8 +11173,8 @@ connectWS();
11260
11173
  }
11261
11174
 
11262
11175
  // src/daemon/daemon-main.ts
11263
- var CONFIG_DIR4 = join8(homedir8(), ".xbrowser");
11264
- var LOG_FILE = join8(CONFIG_DIR4, "daemon.log");
11176
+ var CONFIG_DIR3 = join7(homedir7(), ".xbrowser");
11177
+ var LOG_FILE = join7(CONFIG_DIR3, "daemon.log");
11265
11178
  function log(msg) {
11266
11179
  const ts = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").substring(0, 19);
11267
11180
  const line = `[DAEMON ${ts}] ${msg}
@@ -11293,7 +11206,7 @@ async function main() {
11293
11206
  if (err.code === "EADDRINUSE") {
11294
11207
  log(`Port ${daemonPort} already in use \u2014 another daemon instance likely won the startup race. Exiting gracefully.`);
11295
11208
  try {
11296
- unlinkSync(join8(CONFIG_DIR4, "daemon.json"));
11209
+ unlinkSync(join7(CONFIG_DIR3, "daemon.json"));
11297
11210
  } catch {
11298
11211
  }
11299
11212
  process.exit(0);
@@ -11327,8 +11240,8 @@ async function main() {
11327
11240
  rpcHandler.setPreviewWS(previewWS);
11328
11241
  previewWS.on("screencast-started", (sid) => log(`Preview screencast started: ${sid}`));
11329
11242
  previewWS.on("screencast-stopped", (sid) => log(`Preview screencast stopped: ${sid}`));
11330
- mkdirSync6(CONFIG_DIR4, { recursive: true });
11331
- writeFileSync6(join8(CONFIG_DIR4, "daemon.json"), JSON.stringify({
11243
+ mkdirSync5(CONFIG_DIR3, { recursive: true });
11244
+ writeFileSync5(join7(CONFIG_DIR3, "daemon.json"), JSON.stringify({
11332
11245
  port: daemonPort,
11333
11246
  pid: process.pid,
11334
11247
  startedAt: Date.now()