@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.
- package/dist/{chunk-GJAV3QGG.js → chunk-ANVL2ID2.js} +27 -3
- package/dist/{chunk-2QQDTXDL.js → chunk-PIWNMC36.js} +13 -1
- package/dist/{chunk-MJFYLKGL.js → chunk-X46HDAOT.js} +27 -3
- package/dist/cli.js +246 -132
- package/dist/daemon-main.js +142 -107
- package/dist/{extract-RM62AJXW.js → extract-DSU2RVMN.js} +1 -1
- package/dist/{extract-O46CC533.js → extract-EUWPRSKH.js} +27 -3
- package/dist/{filter-K6FGRJQU.js → filter-3DXC6432.js} +1 -1
- package/dist/{filter-TAAYMSYI.js → filter-7YOPVPVC.js} +27 -3
- package/dist/index.js +247 -133
- package/dist/{plugin-singleton-SYJF6BD6.js → plugin-singleton-FCNDN3FX.js} +1 -1
- package/package.json +3 -2
package/dist/daemon-main.js
CHANGED
|
@@ -26,7 +26,7 @@ import "./chunk-VJNMAWPZ.js";
|
|
|
26
26
|
import "./chunk-TNEN6VQ2.js";
|
|
27
27
|
import {
|
|
28
28
|
getPluginLoader
|
|
29
|
-
} from "./chunk-
|
|
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
|
|
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
|
-
|
|
989
|
-
|
|
990
|
-
|
|
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
|
-
|
|
1014
|
-
(
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
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
|
-
|
|
1029
|
-
|
|
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
|
-
|
|
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
|
|
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: "
|
|
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
|
|
1283
|
+
return fail4("Must provide index or name");
|
|
1250
1284
|
}
|
|
1251
1285
|
if (!targetFrame) {
|
|
1252
|
-
return
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
5305
|
+
return handleClose(p, ctx);
|
|
5272
5306
|
case "switch":
|
|
5273
5307
|
return handleSwitch(p, pages, ctx);
|
|
5274
5308
|
default:
|
|
5275
|
-
return
|
|
5309
|
+
return fail9(`Unknown subcommand: ${p.subcommand}`);
|
|
5276
5310
|
}
|
|
5277
5311
|
}
|
|
5278
5312
|
});
|
|
5279
|
-
function handleList(pages, ctx) {
|
|
5280
|
-
const
|
|
5281
|
-
|
|
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
|
-
|
|
5284
|
-
|
|
5285
|
-
|
|
5286
|
-
|
|
5287
|
-
|
|
5288
|
-
|
|
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 (!/^
|
|
5331
|
+
if (!/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(url)) {
|
|
5308
5332
|
url = "https://" + url;
|
|
5309
5333
|
}
|
|
5310
|
-
|
|
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,
|
|
5331
|
-
|
|
5332
|
-
|
|
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 ??
|
|
5335
|
-
if (closeIndex < 0 || closeIndex >=
|
|
5336
|
-
return
|
|
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 =
|
|
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 :
|
|
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
|
|
5390
|
+
return fail9("Parameter --index is required for switch subcommand");
|
|
5360
5391
|
}
|
|
5361
5392
|
if (p.index < 0 || p.index >= pages.length) {
|
|
5362
|
-
return
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 { ...
|
|
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 { ...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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-
|
|
8043
|
+
const { resetPluginLoader } = await import("./plugin-singleton-FCNDN3FX.js");
|
|
8013
8044
|
resetPluginLoader();
|
|
8014
|
-
const loader = await import("./plugin-singleton-
|
|
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:
|
|
8261
|
-
mkdirSync6(
|
|
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
|
-
|
|
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) {
|
|
@@ -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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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) {
|
|
@@ -24,9 +24,33 @@ var DEFAULT_EXCLUDE_TYPES = [
|
|
|
24
24
|
"mousedown"
|
|
25
25
|
];
|
|
26
26
|
function filterRecording(inputPath, outputPath, excludeTypes) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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) => {
|