@xbrowser/cli 1.9.0 → 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.
package/dist/cli.js CHANGED
@@ -182,7 +182,7 @@ function asZodSchema(value) {
182
182
  // src/executor.ts
183
183
  import {
184
184
  ok as ok25,
185
- fail as fail9,
185
+ fail as fail11,
186
186
  isCommandResult,
187
187
  CompositeStorage as CompositeStorage2,
188
188
  TipCollector as TipCollector2,
@@ -953,7 +953,7 @@ var evaluateCommand = registerCommand({
953
953
 
954
954
  // src/commands/storage.ts
955
955
  import { z as z8 } from "zod";
956
- import { ok as ok8 } from "@dyyz1993/xcli-core";
956
+ import { ok as ok8, fail as fail2 } from "@dyyz1993/xcli-core";
957
957
  var getCookiesCommand = registerCommand({
958
958
  name: "get-cookies",
959
959
  description: "Get all cookies for the current page",
@@ -975,6 +975,7 @@ var setCookieCommand = registerCommand({
975
975
  value: z8.coerce.string(),
976
976
  domain: z8.coerce.string().optional(),
977
977
  path: z8.coerce.string().optional(),
978
+ url: z8.string().optional().describe("Cookie URL (alternative to domain)"),
978
979
  expires: z8.number().optional(),
979
980
  httpOnly: z8.boolean().optional(),
980
981
  secure: z8.boolean().optional(),
@@ -994,6 +995,9 @@ var setCookieCommand = registerCommand({
994
995
  }
995
996
  }
996
997
  }
998
+ if (!cookie.domain && !cookie.url) {
999
+ return fail2("set-cookie requires --domain or --url, or a non-blank page URL to infer from");
1000
+ }
997
1001
  await ctx.browserContext.addCookies([cookie]);
998
1002
  return ok8({ name: p.name });
999
1003
  }
@@ -1020,19 +1024,23 @@ var getLocalStorageCommand = registerCommand({
1020
1024
  z8.object({ data: z8.record(z8.string()) })
1021
1025
  ]),
1022
1026
  handler: async (p, ctx) => {
1023
- if (p.key) {
1024
- const value = await ctx.page.evaluate((k) => localStorage.getItem(k), p.key);
1025
- return ok8({ key: p.key, value });
1026
- }
1027
- const data = await ctx.page.evaluate(() => {
1028
- const entries = {};
1029
- for (let i = 0; i < localStorage.length; i++) {
1030
- const key = localStorage.key(i);
1031
- if (key) entries[key] = localStorage.getItem(key) ?? "";
1027
+ try {
1028
+ if (p.key) {
1029
+ const value = await ctx.page.evaluate((k) => localStorage.getItem(k), p.key);
1030
+ return ok8({ key: p.key, value });
1032
1031
  }
1033
- return entries;
1034
- });
1035
- return ok8({ data });
1032
+ const data = await ctx.page.evaluate(() => {
1033
+ const entries = {};
1034
+ for (let i = 0; i < localStorage.length; i++) {
1035
+ const key = localStorage.key(i);
1036
+ if (key) entries[key] = localStorage.getItem(key) ?? "";
1037
+ }
1038
+ return entries;
1039
+ });
1040
+ return ok8({ data });
1041
+ } catch (e) {
1042
+ return fail2(`localStorage not accessible on this page: ${e instanceof Error ? e.message : String(e)}`);
1043
+ }
1036
1044
  }
1037
1045
  });
1038
1046
  var setLocalStorageCommand = registerCommand({
@@ -1045,13 +1053,17 @@ var setLocalStorageCommand = registerCommand({
1045
1053
  }),
1046
1054
  result: z8.object({ key: z8.string() }),
1047
1055
  handler: async (p, ctx) => {
1048
- await ctx.page.evaluate(
1049
- (args) => {
1050
- localStorage.setItem(args.key, args.value);
1051
- },
1052
- { key: p.key, value: p.value }
1053
- );
1054
- return ok8({ key: p.key });
1056
+ try {
1057
+ await ctx.page.evaluate(
1058
+ (args) => {
1059
+ localStorage.setItem(args.key, args.value);
1060
+ },
1061
+ { key: p.key, value: p.value }
1062
+ );
1063
+ return ok8({ key: p.key });
1064
+ } catch (e) {
1065
+ return fail2(`localStorage not accessible on this page: ${e instanceof Error ? e.message : String(e)}`);
1066
+ }
1055
1067
  }
1056
1068
  });
1057
1069
  var clearLocalStorageCommand = registerCommand({
@@ -1060,21 +1072,35 @@ var clearLocalStorageCommand = registerCommand({
1060
1072
  scope: "page",
1061
1073
  result: z8.object({ cleared: z8.boolean() }),
1062
1074
  handler: async (_p, ctx) => {
1063
- await ctx.page.evaluate(() => localStorage.clear());
1064
- return ok8({ cleared: true });
1075
+ try {
1076
+ await ctx.page.evaluate(() => localStorage.clear());
1077
+ return ok8({ cleared: true });
1078
+ } catch (e) {
1079
+ return fail2(`localStorage not accessible on this page: ${e instanceof Error ? e.message : String(e)}`);
1080
+ }
1065
1081
  }
1066
1082
  });
1067
1083
 
1068
1084
  // src/commands/screenshot.ts
1069
1085
  import { z as z9 } from "zod";
1070
- import { ok as ok9 } from "@dyyz1993/xcli-core";
1086
+ import { ok as ok9, fail as fail3 } from "@dyyz1993/xcli-core";
1071
1087
  import { writeFileSync, mkdirSync } from "fs";
1072
- import { join as join2 } from "path";
1088
+ import { dirname, join as join2 } from "path";
1073
1089
  import { homedir as homedir2 } from "os";
1074
1090
  var SCREENSHOTS_DIR = join2(homedir2(), ".xbrowser", "screenshots");
1075
1091
  function ensureScreenshotsDir() {
1076
1092
  mkdirSync(SCREENSHOTS_DIR, { recursive: true });
1077
1093
  }
1094
+ function ensureParentDir(filePath) {
1095
+ const dir = dirname(filePath);
1096
+ if (dir === "." || dir === "/") return null;
1097
+ try {
1098
+ mkdirSync(dir, { recursive: true });
1099
+ return null;
1100
+ } catch (err) {
1101
+ return err instanceof Error ? err.message : String(err);
1102
+ }
1103
+ }
1078
1104
  function generateScreenshotPath(format) {
1079
1105
  const timestamp = Date.now();
1080
1106
  const random = Math.random().toString(36).slice(2, 8);
@@ -1118,7 +1144,15 @@ var screenshotCommand = registerCommand({
1118
1144
  buffer = await ctx.page.screenshot(options);
1119
1145
  }
1120
1146
  if (p.output) {
1121
- writeFileSync(p.output, buffer, "binary");
1147
+ const dirErr = ensureParentDir(p.output);
1148
+ if (dirErr) {
1149
+ return fail3(`Cannot create directory for --output "${p.output}": ${dirErr}`);
1150
+ }
1151
+ try {
1152
+ writeFileSync(p.output, buffer, "binary");
1153
+ } catch (err) {
1154
+ return fail3(`Failed to write screenshot to "${p.output}": ${err instanceof Error ? err.message : String(err)}`);
1155
+ }
1122
1156
  return ok9({
1123
1157
  output: p.output,
1124
1158
  format,
@@ -1236,7 +1270,7 @@ var setViewportCommand = registerCommand({
1236
1270
 
1237
1271
  // src/commands/frame.ts
1238
1272
  import { z as z12 } from "zod";
1239
- import { ok as ok12, fail as fail2 } from "@dyyz1993/xcli-core";
1273
+ import { ok as ok12, fail as fail4 } from "@dyyz1993/xcli-core";
1240
1274
  var framesCommand = registerCommand({
1241
1275
  name: "frames",
1242
1276
  description: "List all frames in the current page",
@@ -1261,10 +1295,10 @@ var framesCommand = registerCommand({
1261
1295
  });
1262
1296
  var frameCommand = registerCommand({
1263
1297
  name: "frame",
1264
- description: "Switch to a frame by index or name",
1298
+ description: "Get frame info by index or name",
1265
1299
  scope: "page",
1266
1300
  parameters: z12.object({
1267
- index: z12.number().optional(),
1301
+ index: z12.number().int().min(0).optional(),
1268
1302
  name: z12.string().optional()
1269
1303
  }),
1270
1304
  result: z12.object({
@@ -1281,10 +1315,10 @@ var frameCommand = registerCommand({
1281
1315
  } else if (p.name !== void 0) {
1282
1316
  targetFrame = rawFrames.find((f) => f.name() === p.name);
1283
1317
  } else {
1284
- return fail2("Must provide index or name");
1318
+ return fail4("Must provide index or name");
1285
1319
  }
1286
1320
  if (!targetFrame) {
1287
- return fail2("Frame not found");
1321
+ return fail4("Frame not found");
1288
1322
  }
1289
1323
  return ok12({
1290
1324
  name: targetFrame.name(),
@@ -1930,7 +1964,7 @@ var actionsCommand = registerCommand({
1930
1964
 
1931
1965
  // src/commands/scrape.ts
1932
1966
  import { z as z15 } from "zod";
1933
- import { ok as ok15, fail as fail3 } from "@dyyz1993/xcli-core";
1967
+ import { ok as ok15, fail as fail5 } from "@dyyz1993/xcli-core";
1934
1968
 
1935
1969
  // src/lib/html-to-markdown.ts
1936
1970
  import * as cheerio from "cheerio";
@@ -2331,7 +2365,7 @@ var scrapeCommand = registerCommand({
2331
2365
  try {
2332
2366
  const targetUrl = p.url || page.url();
2333
2367
  if (!targetUrl || targetUrl === "about:blank") {
2334
- return fail3("URL is required. Provide a URL or connect to a browser (--cdp) with a non-blank page.");
2368
+ return fail5("URL is required. Provide a URL or connect to a browser (--cdp) with a non-blank page.");
2335
2369
  }
2336
2370
  let lastError;
2337
2371
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
@@ -2485,7 +2519,7 @@ var scrapeCommand = registerCommand({
2485
2519
  }
2486
2520
  }
2487
2521
  }
2488
- return fail3(`Scrape failed after ${maxAttempts} attempt(s): ${lastError?.message ?? "unknown error"}`);
2522
+ return fail5(`Scrape failed after ${maxAttempts} attempt(s): ${lastError?.message ?? "unknown error"}`);
2489
2523
  } finally {
2490
2524
  await closeEphemeralContext(context);
2491
2525
  }
@@ -2498,7 +2532,7 @@ var scrapeCommand = registerCommand({
2498
2532
 
2499
2533
  // src/commands/map.ts
2500
2534
  import { z as z16 } from "zod";
2501
- import { ok as ok16, fail as fail4 } from "@dyyz1993/xcli-core";
2535
+ import { ok as ok16, fail as fail6 } from "@dyyz1993/xcli-core";
2502
2536
 
2503
2537
  // src/utils/url.ts
2504
2538
  var SKIP_EXTENSIONS = /* @__PURE__ */ new Set([
@@ -2747,7 +2781,7 @@ var mapCommand = registerCommand({
2747
2781
  try {
2748
2782
  const targetUrl = p.url || page.url();
2749
2783
  if (!targetUrl || targetUrl === "about:blank") {
2750
- return fail4("URL is required. Provide a URL or connect to a browser (--cdp) with a non-blank page.");
2784
+ return fail6("URL is required. Provide a URL or connect to a browser (--cdp) with a non-blank page.");
2751
2785
  }
2752
2786
  const links = await discoverUrls(page, targetUrl, {
2753
2787
  sitemap: p.sitemap,
@@ -3592,7 +3626,7 @@ ${errors.map((e) => ` - ${e.engine}: ${e.error}`).join("\n")}`
3592
3626
 
3593
3627
  // src/commands/network.ts
3594
3628
  import { z as z19 } from "zod";
3595
- import { ok as ok19, fail as fail5 } from "@dyyz1993/xcli-core";
3629
+ import { ok as ok19, fail as fail7 } from "@dyyz1993/xcli-core";
3596
3630
  function extractPath2(url) {
3597
3631
  try {
3598
3632
  const u = new URL(url);
@@ -3757,7 +3791,7 @@ var networkCommand = registerCommand({
3757
3791
  };
3758
3792
  if (p.listen) {
3759
3793
  const page2 = ctx.page;
3760
- if (!page2) return fail5("No active page. Use --cdp to connect first.");
3794
+ if (!page2) return fail7("No active page. Use --cdp to connect first.");
3761
3795
  const captures = [];
3762
3796
  const consoleMessages = [];
3763
3797
  const wsCaptures = [];
@@ -4137,7 +4171,7 @@ var ENGINE_KEY_ENUM = z20.enum(ALL_ENGINE_KEYS);
4137
4171
 
4138
4172
  // src/commands/snapshot.ts
4139
4173
  import { z as z21 } from "zod";
4140
- import { ok as ok20, fail as fail6, normalizeTips as normalizeTips3 } from "@dyyz1993/xcli-core";
4174
+ import { ok as ok20, fail as fail8, normalizeTips as normalizeTips3 } from "@dyyz1993/xcli-core";
4141
4175
 
4142
4176
  // src/runtime/ref-store.ts
4143
4177
  var sessions = /* @__PURE__ */ new Map();
@@ -4379,7 +4413,7 @@ async function resolveRefParams(page, params, selectorKeys, cache, sessionId) {
4379
4413
 
4380
4414
  // src/utils/site-semantics.ts
4381
4415
  import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync, readFileSync } from "fs";
4382
- import { join as join4, dirname } from "path";
4416
+ import { join as join4, dirname as dirname2 } from "path";
4383
4417
  import { homedir as homedir4 } from "os";
4384
4418
  import { stringify, parse } from "yaml";
4385
4419
  import { execFile } from "child_process";
@@ -4472,7 +4506,7 @@ function extractDomain2(url) {
4472
4506
  }
4473
4507
  function saveSemantics(domain, pagePath, url, elements) {
4474
4508
  const filePath = getSemanticsPath(domain);
4475
- const dir = dirname(filePath);
4509
+ const dir = dirname2(filePath);
4476
4510
  let site;
4477
4511
  if (existsSync(filePath)) {
4478
4512
  try {
@@ -5108,7 +5142,7 @@ var snapshotCommand = registerCommand({
5108
5142
  persistSemantics(url, aria);
5109
5143
  return ok20({ url, title, aria, text, dom }, normalizeTips3(tips));
5110
5144
  }
5111
- return fail6(`Unknown snapshot type: ${p.type}`);
5145
+ return fail8(`Unknown snapshot type: ${p.type}`);
5112
5146
  }
5113
5147
  });
5114
5148
  function persistSemantics(url, aria) {
@@ -5277,7 +5311,7 @@ var waitForCommand = registerCommand({
5277
5311
 
5278
5312
  // src/commands/tab.ts
5279
5313
  import { z as z23 } from "zod";
5280
- import { ok as ok22, fail as fail7 } from "@dyyz1993/xcli-core";
5314
+ import { ok as ok22, fail as fail9 } from "@dyyz1993/xcli-core";
5281
5315
  var TabParams = z23.object({
5282
5316
  subcommand: z23.enum(["list", "new", "close", "switch"]),
5283
5317
  url: z23.string().optional(),
@@ -5294,7 +5328,7 @@ var tabCommand = registerCommand({
5294
5328
  }),
5295
5329
  handler: async (p, ctx) => {
5296
5330
  if (!ctx.browserContext) {
5297
- return fail7("No browser context available. Use --cdp to connect to a browser first.");
5331
+ return fail9("No browser context available. Use --cdp to connect to a browser first.");
5298
5332
  }
5299
5333
  const pages = ctx.browserContext.pages();
5300
5334
  switch (p.subcommand) {
@@ -5303,50 +5337,45 @@ var tabCommand = registerCommand({
5303
5337
  case "new":
5304
5338
  return handleNew(p, pages, ctx);
5305
5339
  case "close":
5306
- return handleClose(p, pages, ctx);
5340
+ return handleClose(p, ctx);
5307
5341
  case "switch":
5308
5342
  return handleSwitch(p, pages, ctx);
5309
5343
  default:
5310
- return fail7(`Unknown subcommand: ${p.subcommand}`);
5344
+ return fail9(`Unknown subcommand: ${p.subcommand}`);
5311
5345
  }
5312
5346
  }
5313
5347
  });
5314
- function handleList(pages, ctx) {
5315
- const currentIndex = pages.indexOf(ctx.page);
5316
- const tabs = pages.map((page, i) => {
5348
+ async function handleList(pages, ctx) {
5349
+ const tabs = [];
5350
+ let activeIndex = -1;
5351
+ for (let i = 0; i < pages.length; i++) {
5352
+ const page = pages[i];
5317
5353
  const url = page.url();
5318
- let title = "";
5319
- try {
5320
- const t = page.title();
5321
- if (t instanceof Promise) {
5322
- void t.then((v) => {
5323
- title = v;
5324
- });
5325
- }
5326
- } catch {
5327
- title = "";
5328
- }
5329
- return {
5330
- index: i,
5331
- url,
5332
- title,
5333
- active: i === currentIndex
5334
- };
5335
- });
5336
- return ok22({ tabs, total: tabs.length, activeIndex: currentIndex });
5354
+ const title = await page.title().catch(() => "");
5355
+ const isActive = page === ctx.page;
5356
+ if (isActive) activeIndex = i;
5357
+ tabs.push({ index: i, url, title, active: isActive });
5358
+ }
5359
+ return ok22({ tabs, total: tabs.length, activeIndex });
5337
5360
  }
5338
5361
  async function handleNew(p, _pages, ctx) {
5339
5362
  const newPage = await ctx.browserContext.newPage();
5363
+ const warnings = [];
5340
5364
  if (p.url) {
5341
5365
  let url = p.url;
5342
- if (!/^https?:\/\//i.test(url)) {
5366
+ if (!/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(url)) {
5343
5367
  url = "https://" + url;
5344
5368
  }
5345
- await newPage.goto(url, { waitUntil: "domcontentloaded", timeout: 15e3 }).catch(() => {
5346
- });
5369
+ try {
5370
+ await newPage.goto(url, { waitUntil: "domcontentloaded", timeout: 15e3 });
5371
+ } catch (err) {
5372
+ warnings.push(`Navigation to "${url}" failed: ${err instanceof Error ? err.message : String(err)}`);
5373
+ }
5374
+ }
5375
+ try {
5376
+ await newPage.waitForLoadState("domcontentloaded");
5377
+ } catch {
5347
5378
  }
5348
- await newPage.waitForLoadState("domcontentloaded").catch(() => {
5349
- });
5350
5379
  const session = ctx.sessionId ? getSessionById(ctx.sessionId) : void 0;
5351
5380
  if (session) {
5352
5381
  setActivePage(session, newPage);
@@ -5359,18 +5388,20 @@ async function handleNew(p, _pages, ctx) {
5359
5388
  index: newIndex >= 0 ? newIndex : allPages.length - 1,
5360
5389
  url: newPage.url(),
5361
5390
  title,
5362
- total: allPages.length
5391
+ total: allPages.length,
5392
+ ...warnings.length > 0 ? { warning: warnings.join("; ") } : {}
5363
5393
  });
5364
5394
  }
5365
- async function handleClose(p, pages, ctx) {
5366
- if (pages.length <= 1) {
5367
- return fail7("Cannot close the last remaining tab");
5395
+ async function handleClose(p, ctx) {
5396
+ const currentPages = ctx.browserContext.pages();
5397
+ if (currentPages.length <= 1) {
5398
+ return fail9("Cannot close the last remaining tab");
5368
5399
  }
5369
- const closeIndex = p.index ?? pages.indexOf(ctx.page);
5370
- if (closeIndex < 0 || closeIndex >= pages.length) {
5371
- return fail7(`Invalid tab index: ${closeIndex}. Valid range: 0-${pages.length - 1}`);
5400
+ const closeIndex = p.index ?? currentPages.findIndex((pg) => pg === ctx.page);
5401
+ if (closeIndex < 0 || closeIndex >= currentPages.length) {
5402
+ return fail9(`Invalid tab index: ${closeIndex}. Valid range: 0-${currentPages.length - 1}`);
5372
5403
  }
5373
- const pageToClose = pages[closeIndex];
5404
+ const pageToClose = currentPages[closeIndex];
5374
5405
  const isActivePage = pageToClose === ctx.page;
5375
5406
  await pageToClose.close();
5376
5407
  const remainingPages = ctx.browserContext.pages();
@@ -5386,15 +5417,15 @@ async function handleClose(p, pages, ctx) {
5386
5417
  return ok22({
5387
5418
  closedIndex: closeIndex,
5388
5419
  total: remainingPages.length,
5389
- activeIndex: isActivePage ? closeIndex < remainingPages.length ? closeIndex : remainingPages.length - 1 : pages.indexOf(ctx.page)
5420
+ activeIndex: isActivePage ? closeIndex < remainingPages.length ? closeIndex : remainingPages.length - 1 : remainingPages.findIndex((pg) => pg === ctx.page)
5390
5421
  });
5391
5422
  }
5392
5423
  async function handleSwitch(p, pages, ctx) {
5393
5424
  if (p.index === void 0) {
5394
- return fail7("Parameter --index is required for switch subcommand");
5425
+ return fail9("Parameter --index is required for switch subcommand");
5395
5426
  }
5396
5427
  if (p.index < 0 || p.index >= pages.length) {
5397
- return fail7(`Invalid tab index: ${p.index}. Valid range: 0-${pages.length - 1}`);
5428
+ return fail9(`Invalid tab index: ${p.index}. Valid range: 0-${pages.length - 1}`);
5398
5429
  }
5399
5430
  const targetPage = pages[p.index];
5400
5431
  await targetPage.bringToFront().catch(() => {
@@ -5646,7 +5677,7 @@ registerCommandDefinition("addinitscript", ["script"]);
5646
5677
 
5647
5678
  // src/commands/find.ts
5648
5679
  import { z as z25 } from "zod";
5649
- import { ok as ok24, fail as fail8, normalizeTips as normalizeTips5 } from "@dyyz1993/xcli-core";
5680
+ import { ok as ok24, fail as fail10, normalizeTips as normalizeTips5 } from "@dyyz1993/xcli-core";
5650
5681
  var actionSchema2 = z25.enum(["click", "fill", "type", "select", "hover", "check"]);
5651
5682
  var findCommand = registerCommand({
5652
5683
  name: "find",
@@ -5687,7 +5718,7 @@ var findCommand = registerCommand({
5687
5718
  });
5688
5719
  const count = await locator.count();
5689
5720
  if (count === 0) {
5690
- return fail8(`No element found with ${p.strategy}="${p.value}"`);
5721
+ return fail10(`No element found with ${p.strategy}="${p.value}"`);
5691
5722
  }
5692
5723
  const tips = [];
5693
5724
  const target = selectTarget(locator, p.strategy);
@@ -5699,15 +5730,15 @@ var findCommand = registerCommand({
5699
5730
  await target.click({ timeout: p.timeout, force: true });
5700
5731
  return okWithTips({ matched: count, selector, action: "click" }, tips);
5701
5732
  } else if (actionName === "fill") {
5702
- if (actionValue === void 0) return fail8("find fill requires a value");
5733
+ if (actionValue === void 0) return fail10("find fill requires a value");
5703
5734
  await target.fill(actionValue, { timeout: p.timeout, force: true });
5704
5735
  return okWithTips({ matched: count, selector, action: `fill("${actionValue}")` }, tips);
5705
5736
  } else if (actionName === "type") {
5706
- if (actionValue === void 0) return fail8("find type requires a value");
5737
+ if (actionValue === void 0) return fail10("find type requires a value");
5707
5738
  await target.type(actionValue, { delay: 10, timeout: p.timeout });
5708
5739
  return okWithTips({ matched: count, selector, action: `type("${actionValue}")` }, tips);
5709
5740
  } else if (actionName === "select") {
5710
- if (actionValue === void 0) return fail8("find select requires a value");
5741
+ if (actionValue === void 0) return fail10("find select requires a value");
5711
5742
  await target.selectOption(actionValue);
5712
5743
  return okWithTips({ matched: count, selector, action: `select("${actionValue}")` }, tips);
5713
5744
  } else if (actionName === "hover") {
@@ -6480,12 +6511,24 @@ var XBrowserPluginLoader = class {
6480
6511
  // src/utils/plugin-singleton.ts
6481
6512
  var pluginLoader = null;
6482
6513
  var pluginsScanned = false;
6514
+ function silenceSchemaWarnings(fn) {
6515
+ if (process.env.XBROWSER_DEBUG || process.env.VITEST_WORKER_ID) return fn();
6516
+ const originalWarn = console.warn;
6517
+ console.warn = (...args) => {
6518
+ const msg = typeof args[0] === "string" ? args[0] : "";
6519
+ if (msg.includes('has no "result" schema')) return;
6520
+ originalWarn(...args);
6521
+ };
6522
+ return fn().finally(() => {
6523
+ console.warn = originalWarn;
6524
+ });
6525
+ }
6483
6526
  async function getPluginLoader() {
6484
6527
  if (!pluginLoader) {
6485
6528
  pluginLoader = new XBrowserPluginLoader();
6486
6529
  }
6487
6530
  if (!pluginsScanned) {
6488
- await pluginLoader.scanAndLoad();
6531
+ await silenceSchemaWarnings(() => pluginLoader.scanAndLoad());
6489
6532
  pluginsScanned = true;
6490
6533
  }
6491
6534
  return pluginLoader;
@@ -7143,7 +7186,7 @@ async function guardCheck(commandName) {
7143
7186
  }
7144
7187
  }
7145
7188
  function errorResult(message) {
7146
- return { ...fail9(message), duration: 0 };
7189
+ return { ...fail11(message), duration: 0 };
7147
7190
  }
7148
7191
  function tipsToMessages(tips) {
7149
7192
  if (!tips || tips.length === 0) return [];
@@ -7444,7 +7487,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
7444
7487
  duration,
7445
7488
  timestamp: start
7446
7489
  });
7447
- return { ...fail9(errorMessage), duration };
7490
+ return { ...fail11(errorMessage), duration };
7448
7491
  } finally {
7449
7492
  }
7450
7493
  }
@@ -7485,7 +7528,7 @@ async function executeChain(input, options) {
7485
7528
  results.push({
7486
7529
  command: cmdName,
7487
7530
  raw: cmdStr,
7488
- ...fail9(`Plugin "${cmdName}" requires a sub-command`),
7531
+ ...fail11(`Plugin "${cmdName}" requires a sub-command`),
7489
7532
  duration: 0
7490
7533
  });
7491
7534
  if (type === "and") {
@@ -7504,7 +7547,7 @@ async function executeChain(input, options) {
7504
7547
  results.push({
7505
7548
  command: cmdName,
7506
7549
  raw: cmdStr,
7507
- ...fail9(`Unknown command "${subCommand}" for plugin "${cmdName}"`),
7550
+ ...fail11(`Unknown command "${subCommand}" for plugin "${cmdName}"`),
7508
7551
  duration: 0
7509
7552
  });
7510
7553
  if (type === "and") {
@@ -7636,7 +7679,7 @@ async function executeChain(input, options) {
7636
7679
  results.push({
7637
7680
  command: `${cmdName} ${subCommand}`,
7638
7681
  raw: cmdStr,
7639
- ...fail9(errorMessage),
7682
+ ...fail11(errorMessage),
7640
7683
  duration: duration2
7641
7684
  });
7642
7685
  if (type === "and") {
@@ -7970,8 +8013,14 @@ var configBuiltin = {
7970
8013
  "preview.quality",
7971
8014
  "preview.fps"
7972
8015
  ]);
7973
- if (!knownKeys.has(key) && !key.startsWith("browser.") && !key.startsWith("captcha.") && !key.startsWith("preview.")) {
7974
- console.warn(`\u26A0\uFE0F Unknown config key: "${key}". Known keys: browser.*, captcha.*, preview.*`);
8016
+ const isKnownNamespace = key.startsWith("browser.") || key.startsWith("captcha.") || key.startsWith("preview.");
8017
+ if (!knownKeys.has(key) && !isKnownNamespace) {
8018
+ console.error(
8019
+ `Unknown config key: "${key}".
8020
+ Known namespaces: browser.*, captcha.*, preview.*
8021
+ Run "xbrowser config list" to see current keys.`
8022
+ );
8023
+ process.exit(1);
7975
8024
  }
7976
8025
  setConfigValue(key, value);
7977
8026
  console.log(`Set ${key} = ${value}`);
@@ -7994,7 +8043,7 @@ import {
7994
8043
  readFileSync as readFileSync8,
7995
8044
  writeFileSync as writeFileSync10
7996
8045
  } from "fs";
7997
- import { resolve as resolve8, basename as basename2, dirname as dirname3 } from "path";
8046
+ import { resolve as resolve8, basename as basename2, dirname as dirname4 } from "path";
7998
8047
  import { homedir as homedir8 } from "os";
7999
8048
 
8000
8049
  // src/plugin/install-sources/local.ts
@@ -8252,7 +8301,7 @@ import {
8252
8301
  rmSync as rmSync5,
8253
8302
  cpSync as cpSync5
8254
8303
  } from "fs";
8255
- import { resolve as resolve7, join as join11, dirname as dirname2 } from "path";
8304
+ import { resolve as resolve7, join as join11, dirname as dirname3 } from "path";
8256
8305
  import { tmpdir as tmpdir5 } from "os";
8257
8306
  import { gunzipSync } from "zlib";
8258
8307
  import {
@@ -8318,7 +8367,7 @@ function extractManifestToDir(manifest, targetDir) {
8318
8367
  mkdirSync7(targetDir, { recursive: true });
8319
8368
  for (const file of manifest) {
8320
8369
  const filePath = resolve7(targetDir, file.path);
8321
- mkdirSync7(dirname2(filePath), { recursive: true });
8370
+ mkdirSync7(dirname3(filePath), { recursive: true });
8322
8371
  writeFileSync9(filePath, Buffer.from(file.content, "base64"));
8323
8372
  }
8324
8373
  }
@@ -8567,7 +8616,7 @@ var PluginInstaller = class {
8567
8616
  if (resp.ok) {
8568
8617
  const content2 = await resp.text();
8569
8618
  const dst = resolve8(sharedDir, file);
8570
- const dstDir = dirname3(dst);
8619
+ const dstDir = dirname4(dst);
8571
8620
  if (!existsSync10(dstDir)) mkdirSync8(dstDir, { recursive: true });
8572
8621
  writeFileSync10(dst, content2, "utf-8");
8573
8622
  console.log(`\u2705 Downloaded shared/${file} from GitHub`);
@@ -8592,7 +8641,7 @@ var PluginInstaller = class {
8592
8641
  const dst = resolve8(sharedDir, file);
8593
8642
  if (existsSync10(src)) {
8594
8643
  try {
8595
- cpSync6(dirname3(src), dirname3(dst), { recursive: true });
8644
+ cpSync6(dirname4(src), dirname4(dst), { recursive: true });
8596
8645
  console.log(`\u2705 Copied shared/${file} for plugin "${basename2(pluginDir)}"`);
8597
8646
  } catch {
8598
8647
  try {
@@ -8735,7 +8784,7 @@ function outputResult(result, mode = "text") {
8735
8784
  function outputError(message) {
8736
8785
  const formatted = formatter.formatError(message, { color: true, emoji: false });
8737
8786
  console.error(formatted);
8738
- process.exit(1);
8787
+ process.exitCode = 1;
8739
8788
  }
8740
8789
  function outputEnvelope(result, meta, mode) {
8741
8790
  if (mode !== "json" && mode !== "yaml") {
@@ -10205,7 +10254,6 @@ async function handleBrowserCommand(command, args, options, sessionName, mode, c
10205
10254
  break;
10206
10255
  }
10207
10256
  case "scrape":
10208
- if (!args[0]) outputError("Usage: xbrowser scrape <url> [--format markdown|html|text] [--mode raw|clean|compact] [--selector <sel>] [--timeout <ms>]");
10209
10257
  cmdName = "scrape";
10210
10258
  params = {
10211
10259
  url: args[0],
@@ -10217,7 +10265,6 @@ async function handleBrowserCommand(command, args, options, sessionName, mode, c
10217
10265
  };
10218
10266
  break;
10219
10267
  case "map":
10220
- if (!args[0]) outputError("Usage: xbrowser map <url> [--search <query>] [--sitemap include|only] [--include-subdomains] [--limit <n>]");
10221
10268
  cmdName = "map";
10222
10269
  params = {
10223
10270
  url: args[0],
@@ -10352,12 +10399,20 @@ async function handleBrowserCommand(command, args, options, sessionName, mode, c
10352
10399
  }
10353
10400
  }
10354
10401
  const outputFile = options.output;
10355
- if (outputFile && result.success && result.data) {
10356
- const { writeFileSync: writeFileSync12 } = await import("fs");
10357
- const content = typeof result.data === "string" ? result.data : result.data.content || result.data.text || JSON.stringify(result.data, null, 2);
10358
- writeFileSync12(outputFile, content, "utf-8");
10359
- console.log(`
10402
+ const dataObj = result.data;
10403
+ const handlerAlreadyWrote = !!dataObj && typeof dataObj.output === "string";
10404
+ if (outputFile && result.success && result.data && !handlerAlreadyWrote) {
10405
+ const { writeFileSync: writeFileSync12, mkdirSync: mkdirSync10 } = await import("fs");
10406
+ const { dirname: dirname6 } = await import("path");
10407
+ const content = typeof result.data === "string" ? result.data : dataObj.content || dataObj.text || JSON.stringify(result.data, null, 2);
10408
+ try {
10409
+ mkdirSync10(dirname6(outputFile), { recursive: true });
10410
+ writeFileSync12(outputFile, content, "utf-8");
10411
+ console.log(`
10360
10412
  \u{1F4C4} Written to ${outputFile}`);
10413
+ } catch (err) {
10414
+ outputError(`Failed to write --output "${outputFile}": ${err instanceof Error ? err.message : String(err)}`);
10415
+ }
10361
10416
  }
10362
10417
  }
10363
10418
 
@@ -10823,7 +10878,11 @@ Total: ${enrichedPlugins.length} plugins`);
10823
10878
  case "login":
10824
10879
  case "whoami":
10825
10880
  case "logout":
10826
- outputError(`"${sub}" has moved to the marketplace plugin. Use: xbrowser marketplace ${sub}`);
10881
+ outputError(
10882
+ `"plugin ${sub}" is no longer available: the built-in marketplace publisher was removed.
10883
+ To publish a plugin, use npm directly: \`npm publish\` from the plugin directory.
10884
+ See docs/plugin-guide.md for the publishing workflow.`
10885
+ );
10827
10886
  break;
10828
10887
  default:
10829
10888
  console.log(handlePluginHelp());
@@ -11184,23 +11243,51 @@ async function handleConvert(args, _mode) {
11184
11243
  const path3 = await import("path");
11185
11244
  const { default: yaml } = await import("yaml");
11186
11245
  const { generateJSScript, generatePythonScript, generateBashScript } = await import("./convert-LB3GJTLR.js");
11187
- const content = fs3.readFileSync(filePath, "utf-8");
11188
- const recording = yaml.parse(content);
11189
- if (recording.actions && !recording.events) recording.events = recording.actions;
11246
+ let recording;
11247
+ try {
11248
+ const content = fs3.readFileSync(filePath, "utf-8");
11249
+ recording = yaml.parse(content);
11250
+ } catch (e) {
11251
+ console.error(`Error: Failed to read "${filePath}": ${e instanceof Error ? e.message.split("\n")[0] : String(e)}`);
11252
+ process.exit(1);
11253
+ }
11254
+ if (recording === null || typeof recording !== "object" || Array.isArray(recording)) {
11255
+ console.error(`Error: "${filePath}" does not contain a valid recording (expected a YAML/JSON object with events or actions).`);
11256
+ process.exit(1);
11257
+ }
11258
+ const rawActions = recording.actions;
11259
+ if (Array.isArray(rawActions) && !Array.isArray(recording.events)) {
11260
+ recording.events = rawActions.map((a) => {
11261
+ const action = a;
11262
+ const element = action.element ?? {};
11263
+ return {
11264
+ type: action.type,
11265
+ selector: element.selector ?? action.selector,
11266
+ data: {
11267
+ ...action.data,
11268
+ value: action.value ?? action.data?.value,
11269
+ key: action.key ?? action.data?.key,
11270
+ x: action.scrollX ?? action.data?.x,
11271
+ y: action.scrollY ?? action.data?.y
11272
+ }
11273
+ };
11274
+ });
11275
+ }
11190
11276
  const ext = path3.extname(outputPath).toLowerCase();
11277
+ const recordingTyped = recording;
11191
11278
  let script;
11192
11279
  if (ext === ".py") {
11193
- script = generatePythonScript(recording);
11280
+ script = generatePythonScript(recordingTyped);
11194
11281
  } else if (ext === ".sh") {
11195
- script = generateBashScript(recording);
11282
+ script = generateBashScript(recordingTyped);
11196
11283
  } else {
11197
- script = generateJSScript(recording);
11284
+ script = generateJSScript(recordingTyped);
11198
11285
  }
11199
11286
  fs3.writeFileSync(outputPath, script);
11200
11287
  fs3.chmodSync(outputPath, 493);
11201
- const eventCount = (recording.events || recording.actions || []).length;
11288
+ const eventCount = (recordingTyped.events || []).length;
11202
11289
  console.log(`Converted ${filePath} -> ${outputPath}`);
11203
- console.log(` Events: ${eventCount}, Start URL: ${recording.startUrl}`);
11290
+ console.log(` Events: ${eventCount}, Start URL: ${recordingTyped.startUrl}`);
11204
11291
  console.log(` Run: ${ext === ".py" ? "python" : ext === ".sh" ? "./" : "node"} ${outputPath}`);
11205
11292
  }
11206
11293
  async function handleExtract(args, _mode) {
@@ -11209,11 +11296,16 @@ async function handleExtract(args, _mode) {
11209
11296
  console.error("Usage: xbrowser extract <recording.yaml>");
11210
11297
  process.exit(1);
11211
11298
  }
11212
- const { extractAndSave, printExtractSummary } = await import("./extract-O46CC533.js");
11213
- const { summary, outputPath } = extractAndSave(filePath);
11214
- printExtractSummary(summary);
11215
- console.log(`
11299
+ const { extractAndSave, printExtractSummary } = await import("./extract-EUWPRSKH.js");
11300
+ try {
11301
+ const { summary, outputPath } = extractAndSave(filePath);
11302
+ printExtractSummary(summary);
11303
+ console.log(`
11216
11304
  Saved LLM summary: ${outputPath}`);
11305
+ } catch (e) {
11306
+ console.error(`Error: ${e instanceof Error ? e.message : String(e)}`);
11307
+ process.exit(1);
11308
+ }
11217
11309
  }
11218
11310
  async function handleFilter(args, _mode, options) {
11219
11311
  const filePath = args[0];
@@ -11222,16 +11314,21 @@ async function handleFilter(args, _mode, options) {
11222
11314
  console.error("Usage: xbrowser filter <input.yaml> <output.yaml> [--exclude type1,type2]");
11223
11315
  process.exit(1);
11224
11316
  }
11225
- const { filterRecording, parseExcludeTypes } = await import("./filter-TAAYMSYI.js");
11317
+ const { filterRecording, parseExcludeTypes } = await import("./filter-7YOPVPVC.js");
11226
11318
  const excludeArgs = args.slice(2).concat(
11227
11319
  Object.entries(options || {}).flatMap(
11228
11320
  ([k, v]) => k.startsWith("exclude") ? [`--${k}${typeof v === "string" ? "=" + v : ""}`] : []
11229
11321
  )
11230
11322
  );
11231
11323
  const excludeTypes = parseExcludeTypes(excludeArgs);
11232
- const result = filterRecording(filePath, outputPath, excludeTypes);
11233
- console.log(`Filtered ${filePath} -> ${outputPath}`);
11234
- console.log(` Original: ${result.originalCount}, After: ${result.filteredCount}, Removed: ${result.removed} (${result.percentage}%)`);
11324
+ try {
11325
+ const result = filterRecording(filePath, outputPath, excludeTypes);
11326
+ console.log(`Filtered ${filePath} -> ${outputPath}`);
11327
+ console.log(` Original: ${result.originalCount}, After: ${result.filteredCount}, Removed: ${result.removed} (${result.percentage}%)`);
11328
+ } catch (e) {
11329
+ console.error(`Error: ${e instanceof Error ? e.message : String(e)}`);
11330
+ process.exit(1);
11331
+ }
11235
11332
  }
11236
11333
  async function handleGeneratePlugin(sessionName, pluginName, outputDir) {
11237
11334
  const { SessionRecorder: SessionRecorder2 } = await import("./session-recorder-KPU4YQAE.js");
@@ -11402,8 +11499,21 @@ export default createSite({
11402
11499
  url: '${data.startUrl}',
11403
11500
  detect: async (ctx: CommandContext) => {
11404
11501
  const page = ensurePage(ctx);
11405
- // TODO: Add login detection logic
11406
- return false;
11502
+ // Best-effort login detection: check for common logged-in indicators.
11503
+ // Customize these selectors for your site's specific login state.
11504
+ try {
11505
+ const loggedIn = await page.evaluate(() => {
11506
+ const el = document.querySelector(
11507
+ '[class*="avatar"], [class*="user-info"], [class*="logged-in"], ' +
11508
+ '[data-testid*="user"], [data-testid*="avatar"], ' +
11509
+ 'a[href*="logout"], button[class*="logout"]'
11510
+ );
11511
+ return !!el;
11512
+ });
11513
+ return loggedIn;
11514
+ } catch {
11515
+ return false;
11516
+ }
11407
11517
  },
11408
11518
  },
11409
11519
 
@@ -11430,7 +11540,7 @@ async function handleRun(filePath, options) {
11430
11540
  outputError("No commands found in file");
11431
11541
  return;
11432
11542
  }
11433
- const chain = commands.join(" && ");
11543
+ const chain = commands.join(" ; ");
11434
11544
  const chainResult = await executeChain(chain, {
11435
11545
  cdpEndpoint: options?.cdpEndpoint,
11436
11546
  sessionName: options?.sessionName,
@@ -12765,6 +12875,7 @@ async function handleChainInput(input, argv) {
12765
12875
  }
12766
12876
  async function routeCommand(argvIn, stdinCommands) {
12767
12877
  let argv = argvIn;
12878
+ process.exitCode = 0;
12768
12879
  try {
12769
12880
  if (stdinCommands && stdinCommands.length > 0) {
12770
12881
  await handleStdinMode(stdinCommands, argv);
@@ -13162,6 +13273,7 @@ Run "xbrowser ${command} ${subCommand} --help" to see available parameters.`
13162
13273
  const result = await forwardExec(`${command}.${subCommand}`, params, sessionName, cdpEndpoint, userTimeout);
13163
13274
  const resultData = result && typeof result === "object" && "data" in result ? result.data : void 0;
13164
13275
  if (result && result.success === false && resultData?.code === "LOGIN_REQUIRED") {
13276
+ process.exitCode = 1;
13165
13277
  outputLoginRequired(result, mode);
13166
13278
  return;
13167
13279
  }
@@ -13233,6 +13345,7 @@ Run "xbrowser ${command} ${subCommand} --help" to see available parameters.`
13233
13345
  sessionName
13234
13346
  });
13235
13347
  if (!loginGuard.ok) {
13348
+ process.exitCode = 1;
13236
13349
  const result2 = {
13237
13350
  success: false,
13238
13351
  data: loginGuard.data ?? null,
@@ -13276,8 +13389,9 @@ Run "xbrowser ${command} ${subCommand} --help" to see available parameters.`
13276
13389
  }
13277
13390
  const outputData = isCommandResult2(result) ? result.data : result && typeof result === "object" ? result.data ?? result : result;
13278
13391
  const tips = isCommandResult2(result) ? result.tips : result && typeof result === "object" ? result.tips : void 0;
13392
+ const resultSuccess = isCommandResult2(result) ? result.success !== false : true;
13393
+ if (!resultSuccess) process.exitCode = 1;
13279
13394
  if (mode === "json" || mode === "yaml") {
13280
- const resultSuccess = isCommandResult2(result) ? result.success !== false : true;
13281
13395
  const resultMsg = isCommandResult2(result) ? result.message : void 0;
13282
13396
  const duration = Date.now() - cmdStart;
13283
13397
  const envelopeMeta = { command: `${command} ${subCommand}` };
@@ -13472,7 +13586,7 @@ async function main() {
13472
13586
  const { ensureProcessCanExit } = await import("./browser-4KRHALJ3.js");
13473
13587
  await ensureProcessCanExit().catch(() => {
13474
13588
  });
13475
- process.exit(exitCode);
13589
+ process.exit(process.exitCode || exitCode);
13476
13590
  }
13477
13591
  }
13478
13592
  }