@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.
- package/dist/{browser-XPKM344Y.js → browser-4KRHALJ3.js} +1 -1
- package/dist/{browser-LW2MDJE4.js → browser-6WJHQDPV.js} +1 -1
- package/dist/{browser-TLIDFFEG.js → browser-C63PFGPT.js} +1 -1
- package/dist/{chunk-GJAV3QGG.js → chunk-ANVL2ID2.js} +27 -3
- package/dist/{chunk-CJJ564VK.js → chunk-MSQIPXKE.js} +1 -1
- package/dist/{chunk-HXLMMSU3.js → chunk-OOJ2H7IL.js} +1 -1
- package/dist/{chunk-2QQDTXDL.js → chunk-PIWNMC36.js} +13 -1
- package/dist/{chunk-FL6DOSWV.js → chunk-TTZNR3QP.js} +30 -2
- package/dist/{chunk-MJFYLKGL.js → chunk-X46HDAOT.js} +27 -3
- package/dist/{chunk-RMYEHTLS.js → chunk-YSCY52UJ.js} +30 -2
- package/dist/{chunk-SJLRCE6N.js → chunk-Z7WC4P2M.js} +1 -1
- package/dist/cli.js +252 -138
- package/dist/daemon-main.js +162 -249
- 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.d.ts +11 -1
- package/dist/index.js +252 -138
- package/dist/{plugin-singleton-SYJF6BD6.js → plugin-singleton-FCNDN3FX.js} +1 -1
- package/dist/{session-recorder-33UKWCIR.js → session-recorder-KPU4YQAE.js} +1 -1
- package/dist/{session-recorder-BMIJ3ZJM.js → session-recorder-UTULJFTE.js} +1 -1
- package/package.json +3 -2
package/dist/daemon-main.js
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
} from "./chunk-VEDJ5XSQ.js";
|
|
6
6
|
import {
|
|
7
7
|
SessionRecorder
|
|
8
|
-
} from "./chunk-
|
|
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-
|
|
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-
|
|
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
|
|
42
|
-
import { join as
|
|
43
|
-
import { homedir as
|
|
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 {
|
|
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
|
|
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
|
-
|
|
991
|
-
|
|
992
|
-
|
|
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
|
-
|
|
1016
|
-
(
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
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
|
-
|
|
1031
|
-
|
|
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
|
-
|
|
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
|
|
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: "
|
|
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
|
|
1283
|
+
return fail4("Must provide index or name");
|
|
1252
1284
|
}
|
|
1253
1285
|
if (!targetFrame) {
|
|
1254
|
-
return
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
5305
|
+
return handleClose(p, ctx);
|
|
5274
5306
|
case "switch":
|
|
5275
5307
|
return handleSwitch(p, pages, ctx);
|
|
5276
5308
|
default:
|
|
5277
|
-
return
|
|
5309
|
+
return fail9(`Unknown subcommand: ${p.subcommand}`);
|
|
5278
5310
|
}
|
|
5279
5311
|
}
|
|
5280
5312
|
});
|
|
5281
|
-
function handleList(pages, ctx) {
|
|
5282
|
-
const
|
|
5283
|
-
|
|
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
|
-
|
|
5286
|
-
|
|
5287
|
-
|
|
5288
|
-
|
|
5289
|
-
|
|
5290
|
-
|
|
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 (!/^
|
|
5331
|
+
if (!/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(url)) {
|
|
5310
5332
|
url = "https://" + url;
|
|
5311
5333
|
}
|
|
5312
|
-
|
|
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,
|
|
5333
|
-
|
|
5334
|
-
|
|
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 ??
|
|
5337
|
-
if (closeIndex < 0 || closeIndex >=
|
|
5338
|
-
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}`);
|
|
5339
5368
|
}
|
|
5340
|
-
const pageToClose =
|
|
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 :
|
|
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
|
|
5390
|
+
return fail9("Parameter --index is required for switch subcommand");
|
|
5362
5391
|
}
|
|
5363
5392
|
if (p.index < 0 || p.index >= pages.length) {
|
|
5364
|
-
return
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 { ...
|
|
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-
|
|
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 { ...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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-
|
|
8043
|
+
const { resetPluginLoader } = await import("./plugin-singleton-FCNDN3FX.js");
|
|
8079
8044
|
resetPluginLoader();
|
|
8080
|
-
const loader = await import("./plugin-singleton-
|
|
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:
|
|
8364
|
-
|
|
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:
|
|
8382
|
-
const { dirname:
|
|
8383
|
-
|
|
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
|
-
|
|
8295
|
+
writeFileSync6(outputPath, yaml2.stringify(data), "utf-8");
|
|
8387
8296
|
} else {
|
|
8388
|
-
|
|
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
|
-
|
|
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:
|
|
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(
|
|
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
|
|
11264
|
-
var LOG_FILE =
|
|
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(
|
|
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
|
-
|
|
11331
|
-
|
|
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()
|