@xbrowser/cli 1.9.6 → 1.9.8
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-ALPLJNH2.js → browser-43YXUIML.js} +2 -2
- package/dist/{browser-JXIJF4VL.js → browser-GT4BMX6X.js} +1 -1
- package/dist/{browser-ZPQP6VW7.js → browser-SJWPJFN7.js} +2 -2
- package/dist/{cdp-driver-6EVYLHL2.js → cdp-driver-PUZLDSQU.js} +1 -1
- package/dist/{cdp-driver-MI7VCE44.js → cdp-driver-WAFYSIIW.js} +16 -1
- package/dist/{chunk-74S7UUI2.js → chunk-A4YHJW3Z.js} +17 -2
- package/dist/{chunk-CH5ZQV64.js → chunk-C3LIGO4V.js} +22 -2
- package/dist/{chunk-37SHZSUR.js → chunk-ENCEMM7Z.js} +2 -2
- package/dist/{chunk-VJNMAWPZ.js → chunk-GM3HWQ5U.js} +16 -1
- package/dist/{chunk-5JSOGINQ.js → chunk-PBDID7SE.js} +2 -2
- package/dist/{chunk-C4O73DQL.js → chunk-VEWTHYDG.js} +22 -2
- package/dist/cli.js +66 -11
- package/dist/daemon-main.js +31 -18
- package/dist/index.d.ts +5 -0
- package/dist/index.js +67 -12
- package/dist/{session-recorder-RFOPX4SF.js → session-recorder-AG6CH6EI.js} +1 -1
- package/dist/{session-recorder-7GXI42FO.js → session-recorder-K3K63Q33.js} +1 -1
- package/dist/{session-replayer-3HEIJBJ2.js → session-replayer-V4MP6M6I.js} +2 -2
- package/package.json +1 -1
|
@@ -20,8 +20,8 @@ import {
|
|
|
20
20
|
saveSessionDiskMeta,
|
|
21
21
|
setActivePage,
|
|
22
22
|
touchSession
|
|
23
|
-
} from "./chunk-
|
|
24
|
-
import "./chunk-
|
|
23
|
+
} from "./chunk-ENCEMM7Z.js";
|
|
24
|
+
import "./chunk-GM3HWQ5U.js";
|
|
25
25
|
import "./chunk-TNEN6VQ2.js";
|
|
26
26
|
import "./chunk-GDKLH7ZY.js";
|
|
27
27
|
import "./chunk-KFQGP6VL.js";
|
|
@@ -20,8 +20,8 @@ import {
|
|
|
20
20
|
saveSessionDiskMeta,
|
|
21
21
|
setActivePage,
|
|
22
22
|
touchSession
|
|
23
|
-
} from "./chunk-
|
|
24
|
-
import "./chunk-
|
|
23
|
+
} from "./chunk-PBDID7SE.js";
|
|
24
|
+
import "./chunk-GM3HWQ5U.js";
|
|
25
25
|
import "./chunk-TNEN6VQ2.js";
|
|
26
26
|
import "./chunk-GDKLH7ZY.js";
|
|
27
27
|
import "./chunk-ABXMBNQ6.js";
|
|
@@ -1333,7 +1333,22 @@ Last error: ${lastError.message}` : "";
|
|
|
1333
1333
|
return this._url;
|
|
1334
1334
|
}
|
|
1335
1335
|
async title() {
|
|
1336
|
-
|
|
1336
|
+
for (let i = 0; i < 20; i++) {
|
|
1337
|
+
try {
|
|
1338
|
+
this._title = await this.evaluate("document.title");
|
|
1339
|
+
if (this._title) return this._title;
|
|
1340
|
+
} catch {
|
|
1341
|
+
}
|
|
1342
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
1343
|
+
}
|
|
1344
|
+
try {
|
|
1345
|
+
const info = await this._cdpSend("Target.getTargetInfo", { targetId: this._targetId });
|
|
1346
|
+
if (info?.title) {
|
|
1347
|
+
this._title = info.title;
|
|
1348
|
+
return this._title;
|
|
1349
|
+
}
|
|
1350
|
+
} catch {
|
|
1351
|
+
}
|
|
1337
1352
|
return this._title;
|
|
1338
1353
|
}
|
|
1339
1354
|
async content() {
|
|
@@ -1338,7 +1338,22 @@ Last error: ${lastError.message}` : "";
|
|
|
1338
1338
|
return this._url;
|
|
1339
1339
|
}
|
|
1340
1340
|
async title() {
|
|
1341
|
-
|
|
1341
|
+
for (let i = 0; i < 20; i++) {
|
|
1342
|
+
try {
|
|
1343
|
+
this._title = await this.evaluate("document.title");
|
|
1344
|
+
if (this._title) return this._title;
|
|
1345
|
+
} catch {
|
|
1346
|
+
}
|
|
1347
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
1348
|
+
}
|
|
1349
|
+
try {
|
|
1350
|
+
const info = await this._cdpSend("Target.getTargetInfo", { targetId: this._targetId });
|
|
1351
|
+
if (info?.title) {
|
|
1352
|
+
this._title = info.title;
|
|
1353
|
+
return this._title;
|
|
1354
|
+
}
|
|
1355
|
+
} catch {
|
|
1356
|
+
}
|
|
1342
1357
|
return this._title;
|
|
1343
1358
|
}
|
|
1344
1359
|
async content() {
|
|
@@ -4965,7 +4980,7 @@ async function closeSessionByName(name) {
|
|
|
4965
4980
|
} catch {
|
|
4966
4981
|
}
|
|
4967
4982
|
try {
|
|
4968
|
-
const { SessionRecorder } = await import("./session-recorder-
|
|
4983
|
+
const { SessionRecorder } = await import("./session-recorder-AG6CH6EI.js");
|
|
4969
4984
|
SessionRecorder.cleanup(session.name);
|
|
4970
4985
|
} catch {
|
|
4971
4986
|
}
|
|
@@ -2020,17 +2020,37 @@ var SessionRecorder = class _SessionRecorder {
|
|
|
2020
2020
|
* selector or the screenshot fails (non-critical — never blocks recording).
|
|
2021
2021
|
*
|
|
2022
2022
|
* Only captures for: click, input, change, dblclick, and their CDP counterparts.
|
|
2023
|
+
*
|
|
2024
|
+
* Retry strategy (3 attempts, 3s each):
|
|
2025
|
+
* 1. element screenshot via locator
|
|
2026
|
+
* 2. retry element screenshot (element may have been animating/detached)
|
|
2027
|
+
* 3. fallback to viewport screenshot (so we always get *something*)
|
|
2023
2028
|
*/
|
|
2024
2029
|
async captureElementScreenshot(page, action) {
|
|
2025
2030
|
const keyTypes = ["click", "input", "change", "dblclick", "cdp-click", "cdp-fill", "filechooser"];
|
|
2026
2031
|
if (!keyTypes.includes(action.type)) return;
|
|
2027
2032
|
if (!action.element?.selector) return;
|
|
2033
|
+
const selector = action.element.selector;
|
|
2028
2034
|
try {
|
|
2029
|
-
const buffer = await page.locator(
|
|
2035
|
+
const buffer = await page.locator(selector).first().screenshot({
|
|
2030
2036
|
type: "png",
|
|
2031
|
-
timeout:
|
|
2037
|
+
timeout: 3e3
|
|
2032
2038
|
});
|
|
2033
2039
|
return buffer.toString("base64");
|
|
2040
|
+
} catch {
|
|
2041
|
+
}
|
|
2042
|
+
try {
|
|
2043
|
+
await page.waitForSelector(selector, { state: "visible", timeout: 2e3 });
|
|
2044
|
+
const buffer = await page.locator(selector).first().screenshot({
|
|
2045
|
+
type: "png",
|
|
2046
|
+
timeout: 3e3
|
|
2047
|
+
});
|
|
2048
|
+
return buffer.toString("base64");
|
|
2049
|
+
} catch {
|
|
2050
|
+
}
|
|
2051
|
+
try {
|
|
2052
|
+
const buffer = await page.screenshot({ type: "png", timeout: 3e3 });
|
|
2053
|
+
return buffer.toString("base64");
|
|
2034
2054
|
} catch {
|
|
2035
2055
|
return;
|
|
2036
2056
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
launch
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-GM3HWQ5U.js";
|
|
4
4
|
import {
|
|
5
5
|
errMsg
|
|
6
6
|
} from "./chunk-GDKLH7ZY.js";
|
|
@@ -2021,7 +2021,7 @@ async function closeSessionByName(name) {
|
|
|
2021
2021
|
} catch {
|
|
2022
2022
|
}
|
|
2023
2023
|
try {
|
|
2024
|
-
const { SessionRecorder } = await import("./session-recorder-
|
|
2024
|
+
const { SessionRecorder } = await import("./session-recorder-K3K63Q33.js");
|
|
2025
2025
|
SessionRecorder.cleanup(session.name);
|
|
2026
2026
|
} catch {
|
|
2027
2027
|
}
|
|
@@ -1332,7 +1332,22 @@ Last error: ${lastError.message}` : "";
|
|
|
1332
1332
|
return this._url;
|
|
1333
1333
|
}
|
|
1334
1334
|
async title() {
|
|
1335
|
-
|
|
1335
|
+
for (let i = 0; i < 20; i++) {
|
|
1336
|
+
try {
|
|
1337
|
+
this._title = await this.evaluate("document.title");
|
|
1338
|
+
if (this._title) return this._title;
|
|
1339
|
+
} catch {
|
|
1340
|
+
}
|
|
1341
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
1342
|
+
}
|
|
1343
|
+
try {
|
|
1344
|
+
const info = await this._cdpSend("Target.getTargetInfo", { targetId: this._targetId });
|
|
1345
|
+
if (info?.title) {
|
|
1346
|
+
this._title = info.title;
|
|
1347
|
+
return this._title;
|
|
1348
|
+
}
|
|
1349
|
+
} catch {
|
|
1350
|
+
}
|
|
1336
1351
|
return this._title;
|
|
1337
1352
|
}
|
|
1338
1353
|
async content() {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
launch
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-GM3HWQ5U.js";
|
|
4
4
|
import {
|
|
5
5
|
errMsg
|
|
6
6
|
} from "./chunk-GDKLH7ZY.js";
|
|
@@ -656,7 +656,7 @@ async function closeSessionByName(name) {
|
|
|
656
656
|
} catch {
|
|
657
657
|
}
|
|
658
658
|
try {
|
|
659
|
-
const { SessionRecorder } = await import("./session-recorder-
|
|
659
|
+
const { SessionRecorder } = await import("./session-recorder-AG6CH6EI.js");
|
|
660
660
|
SessionRecorder.cleanup(session.name);
|
|
661
661
|
} catch {
|
|
662
662
|
}
|
|
@@ -2417,17 +2417,37 @@ var SessionRecorder = class _SessionRecorder {
|
|
|
2417
2417
|
* selector or the screenshot fails (non-critical — never blocks recording).
|
|
2418
2418
|
*
|
|
2419
2419
|
* Only captures for: click, input, change, dblclick, and their CDP counterparts.
|
|
2420
|
+
*
|
|
2421
|
+
* Retry strategy (3 attempts, 3s each):
|
|
2422
|
+
* 1. element screenshot via locator
|
|
2423
|
+
* 2. retry element screenshot (element may have been animating/detached)
|
|
2424
|
+
* 3. fallback to viewport screenshot (so we always get *something*)
|
|
2420
2425
|
*/
|
|
2421
2426
|
async captureElementScreenshot(page, action) {
|
|
2422
2427
|
const keyTypes = ["click", "input", "change", "dblclick", "cdp-click", "cdp-fill", "filechooser"];
|
|
2423
2428
|
if (!keyTypes.includes(action.type)) return;
|
|
2424
2429
|
if (!action.element?.selector) return;
|
|
2430
|
+
const selector = action.element.selector;
|
|
2425
2431
|
try {
|
|
2426
|
-
const buffer = await page.locator(
|
|
2432
|
+
const buffer = await page.locator(selector).first().screenshot({
|
|
2427
2433
|
type: "png",
|
|
2428
|
-
timeout:
|
|
2434
|
+
timeout: 3e3
|
|
2429
2435
|
});
|
|
2430
2436
|
return buffer.toString("base64");
|
|
2437
|
+
} catch {
|
|
2438
|
+
}
|
|
2439
|
+
try {
|
|
2440
|
+
await page.waitForSelector(selector, { state: "visible", timeout: 2e3 });
|
|
2441
|
+
const buffer = await page.locator(selector).first().screenshot({
|
|
2442
|
+
type: "png",
|
|
2443
|
+
timeout: 3e3
|
|
2444
|
+
});
|
|
2445
|
+
return buffer.toString("base64");
|
|
2446
|
+
} catch {
|
|
2447
|
+
}
|
|
2448
|
+
try {
|
|
2449
|
+
const buffer = await page.screenshot({ type: "png", timeout: 3e3 });
|
|
2450
|
+
return buffer.toString("base64");
|
|
2431
2451
|
} catch {
|
|
2432
2452
|
return;
|
|
2433
2453
|
}
|
package/dist/cli.js
CHANGED
|
@@ -30,7 +30,7 @@ import {
|
|
|
30
30
|
} from "./chunk-YAUG3NSI.js";
|
|
31
31
|
import {
|
|
32
32
|
SessionRecorder
|
|
33
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-C3LIGO4V.js";
|
|
34
34
|
import {
|
|
35
35
|
addKnownIssue,
|
|
36
36
|
getKnowledgePath,
|
|
@@ -54,7 +54,7 @@ import {
|
|
|
54
54
|
resolveLaunchOpts,
|
|
55
55
|
saveSessionDiskMeta,
|
|
56
56
|
setActivePage
|
|
57
|
-
} from "./chunk-
|
|
57
|
+
} from "./chunk-A4YHJW3Z.js";
|
|
58
58
|
import "./chunk-TNEN6VQ2.js";
|
|
59
59
|
import {
|
|
60
60
|
errMsg
|
|
@@ -7058,7 +7058,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
7058
7058
|
}
|
|
7059
7059
|
let targetPageOverride = null;
|
|
7060
7060
|
if (_target && extraOpts?.cdpEndpoint) {
|
|
7061
|
-
const { findTargetPage } = await import("./browser-
|
|
7061
|
+
const { findTargetPage } = await import("./browser-GT4BMX6X.js");
|
|
7062
7062
|
targetPageOverride = await findTargetPage(extraOpts.cdpEndpoint, _target);
|
|
7063
7063
|
if (!targetPageOverride) {
|
|
7064
7064
|
return errorResult(`Target "${_target}" not found. Use 'xbrowser targets --cdp ${extraOpts.cdpEndpoint}' to list available pages.`);
|
|
@@ -10325,6 +10325,50 @@ async function infoFromMarketplacePlugin(slug, loader) {
|
|
|
10325
10325
|
}
|
|
10326
10326
|
return null;
|
|
10327
10327
|
}
|
|
10328
|
+
function normalizePluginName(name) {
|
|
10329
|
+
return name.replace(/^@[^/]+\//, "").replace(/^xbrowser-plugin-/, "").toLowerCase();
|
|
10330
|
+
}
|
|
10331
|
+
async function searchLocalPlugins(query) {
|
|
10332
|
+
let installed;
|
|
10333
|
+
try {
|
|
10334
|
+
installed = await new PluginInstaller().list();
|
|
10335
|
+
} catch {
|
|
10336
|
+
return [];
|
|
10337
|
+
}
|
|
10338
|
+
if (!installed || installed.length === 0) return [];
|
|
10339
|
+
const q = normalizePluginName(query);
|
|
10340
|
+
if (!q) return [];
|
|
10341
|
+
const results = [];
|
|
10342
|
+
for (const p of installed) {
|
|
10343
|
+
const plugin = p;
|
|
10344
|
+
const name = String(plugin.name ?? "");
|
|
10345
|
+
const norm = normalizePluginName(name);
|
|
10346
|
+
if (norm === q) {
|
|
10347
|
+
results.push({ plugin, rank: 0 });
|
|
10348
|
+
} else if (norm.startsWith(q) || q.startsWith(norm)) {
|
|
10349
|
+
results.push({ plugin, rank: 1 });
|
|
10350
|
+
} else if (norm.includes(q) || q.includes(norm)) {
|
|
10351
|
+
results.push({ plugin, rank: 2 });
|
|
10352
|
+
}
|
|
10353
|
+
}
|
|
10354
|
+
results.sort((a, b) => a.rank - b.rank);
|
|
10355
|
+
const runtimeInfo = await buildRuntimePluginInfo().catch(() => /* @__PURE__ */ new Map());
|
|
10356
|
+
return results.map(({ plugin }) => {
|
|
10357
|
+
const name = String(plugin.name ?? "");
|
|
10358
|
+
const metadata = plugin.metadata;
|
|
10359
|
+
const staticCommands = metadata?.commands;
|
|
10360
|
+
const rt = runtimeInfo.get(name);
|
|
10361
|
+
const commands = rt?.commands || staticCommands;
|
|
10362
|
+
return {
|
|
10363
|
+
name: "@xbrowser/" + name,
|
|
10364
|
+
slug: name,
|
|
10365
|
+
description: metadata?.description,
|
|
10366
|
+
version: metadata?.version,
|
|
10367
|
+
commands,
|
|
10368
|
+
source: "local"
|
|
10369
|
+
};
|
|
10370
|
+
});
|
|
10371
|
+
}
|
|
10328
10372
|
async function handleSearch(args, options, mode) {
|
|
10329
10373
|
const query = args[0] || "";
|
|
10330
10374
|
applyRegistryOverride(options);
|
|
@@ -10332,6 +10376,8 @@ async function handleSearch(args, options, mode) {
|
|
|
10332
10376
|
const searchLimit = options.limit ? Number(options.limit) : 20;
|
|
10333
10377
|
const searchOpts = { query, tag: options.tag, site: options.site, limit: searchLimit };
|
|
10334
10378
|
const results = [];
|
|
10379
|
+
const localResults = await searchLocalPlugins(query);
|
|
10380
|
+
results.push(...localResults);
|
|
10335
10381
|
const loader = await getPluginLoader();
|
|
10336
10382
|
const pluginResults = await searchFromMarketplacePlugin2(searchOpts, loader);
|
|
10337
10383
|
results.push(...pluginResults);
|
|
@@ -10344,15 +10390,24 @@ async function handleSearch(args, options, mode) {
|
|
|
10344
10390
|
} catch {
|
|
10345
10391
|
}
|
|
10346
10392
|
}
|
|
10393
|
+
const seen = /* @__PURE__ */ new Set();
|
|
10394
|
+
const deduped = [];
|
|
10395
|
+
for (const r of results) {
|
|
10396
|
+
const key = normalizePluginName(String(r.name ?? ""));
|
|
10397
|
+
if (key && !seen.has(key)) {
|
|
10398
|
+
seen.add(key);
|
|
10399
|
+
deduped.push(r);
|
|
10400
|
+
}
|
|
10401
|
+
}
|
|
10347
10402
|
if (mode === "json") {
|
|
10348
|
-
outputEnvelope({ success: true, data: { results, total:
|
|
10403
|
+
outputEnvelope({ success: true, data: { results: deduped, total: deduped.length } }, { command: "plugin search" }, mode);
|
|
10349
10404
|
} else {
|
|
10350
|
-
if (
|
|
10405
|
+
if (deduped.length === 0) {
|
|
10351
10406
|
console.log("No plugins found");
|
|
10352
10407
|
return;
|
|
10353
10408
|
}
|
|
10354
|
-
for (const r of
|
|
10355
|
-
const src = r.source === "marketplace" ? "[marketplace]" : "[npm]";
|
|
10409
|
+
for (const r of deduped) {
|
|
10410
|
+
const src = r.source === "marketplace" ? "[marketplace]" : r.source === "local" ? "[local]" : "[npm]";
|
|
10356
10411
|
const slug = r.slug ? ` (${r.slug})` : "";
|
|
10357
10412
|
console.log(` ${src} ${r.name}${slug}`);
|
|
10358
10413
|
if (r.description) console.log(` ${r.description}`);
|
|
@@ -10360,7 +10415,7 @@ async function handleSearch(args, options, mode) {
|
|
|
10360
10415
|
if (r.downloads) console.log(` Downloads: ${r.downloads}`);
|
|
10361
10416
|
console.log("");
|
|
10362
10417
|
}
|
|
10363
|
-
console.log(`Total: ${
|
|
10418
|
+
console.log(`Total: ${deduped.length} plugins`);
|
|
10364
10419
|
}
|
|
10365
10420
|
}
|
|
10366
10421
|
async function handlePluginInfo(args, options, mode) {
|
|
@@ -11062,7 +11117,7 @@ async function handleFilter(args, _mode, options) {
|
|
|
11062
11117
|
}
|
|
11063
11118
|
}
|
|
11064
11119
|
async function handleGeneratePlugin(sessionName, pluginName, outputDir) {
|
|
11065
|
-
const { SessionRecorder: SessionRecorder2 } = await import("./session-recorder-
|
|
11120
|
+
const { SessionRecorder: SessionRecorder2 } = await import("./session-recorder-AG6CH6EI.js");
|
|
11066
11121
|
const { readSiteKnowledge: readSiteKnowledge2, toMarkdown } = await import("./site-knowledge-SYC6VCDB.js");
|
|
11067
11122
|
const { mkdirSync: mkdirSync10, writeFileSync: writeFileSync12 } = await import("fs");
|
|
11068
11123
|
const { join: join13 } = await import("path");
|
|
@@ -13039,7 +13094,7 @@ Run "xbrowser ${command} ${subCommand} --help" to see available parameters.`
|
|
|
13039
13094
|
const targetPage = pages[cmdTabIndex];
|
|
13040
13095
|
await targetPage.bringToFront().catch(() => {
|
|
13041
13096
|
});
|
|
13042
|
-
const { setActivePage: setActivePage2 } = await import("./browser-
|
|
13097
|
+
const { setActivePage: setActivePage2 } = await import("./browser-GT4BMX6X.js");
|
|
13043
13098
|
setActivePage2(session, targetPage);
|
|
13044
13099
|
}
|
|
13045
13100
|
}
|
|
@@ -13315,7 +13370,7 @@ async function main() {
|
|
|
13315
13370
|
const command = process.argv[2];
|
|
13316
13371
|
const isLongRunning = command === "preview" || command === "serve";
|
|
13317
13372
|
if (!isLongRunning) {
|
|
13318
|
-
const { ensureProcessCanExit } = await import("./browser-
|
|
13373
|
+
const { ensureProcessCanExit } = await import("./browser-GT4BMX6X.js");
|
|
13319
13374
|
await ensureProcessCanExit().catch(() => {
|
|
13320
13375
|
});
|
|
13321
13376
|
process.exit(process.exitCode || exitCode);
|
package/dist/daemon-main.js
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
} from "./chunk-VEDJ5XSQ.js";
|
|
14
14
|
import {
|
|
15
15
|
SessionRecorder
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-VEWTHYDG.js";
|
|
17
17
|
import {
|
|
18
18
|
closeEphemeralContext,
|
|
19
19
|
closeSessionByName,
|
|
@@ -29,8 +29,8 @@ import {
|
|
|
29
29
|
resolveLaunchOpts,
|
|
30
30
|
saveSessionDiskMeta,
|
|
31
31
|
setActivePage
|
|
32
|
-
} from "./chunk-
|
|
33
|
-
import "./chunk-
|
|
32
|
+
} from "./chunk-ENCEMM7Z.js";
|
|
33
|
+
import "./chunk-GM3HWQ5U.js";
|
|
34
34
|
import "./chunk-TNEN6VQ2.js";
|
|
35
35
|
import {
|
|
36
36
|
errMsg
|
|
@@ -6575,7 +6575,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
6575
6575
|
}
|
|
6576
6576
|
let targetPageOverride = null;
|
|
6577
6577
|
if (_target && extraOpts?.cdpEndpoint) {
|
|
6578
|
-
const { findTargetPage } = await import("./browser-
|
|
6578
|
+
const { findTargetPage } = await import("./browser-43YXUIML.js");
|
|
6579
6579
|
targetPageOverride = await findTargetPage(extraOpts.cdpEndpoint, _target);
|
|
6580
6580
|
if (!targetPageOverride) {
|
|
6581
6581
|
return errorResult(`Target "${_target}" not found. Use 'xbrowser targets --cdp ${extraOpts.cdpEndpoint}' to list available pages.`);
|
|
@@ -8167,7 +8167,7 @@ function createRPCHandler() {
|
|
|
8167
8167
|
const sessionName = params.session || "default";
|
|
8168
8168
|
const slowMo = params.slowMo || 1;
|
|
8169
8169
|
if (!file) {
|
|
8170
|
-
return { ok: false,
|
|
8170
|
+
return normalizeReplayResult({ ok: false, errors: [{ eventIndex: -1, error: "Missing file parameter" }] });
|
|
8171
8171
|
}
|
|
8172
8172
|
let rawContent;
|
|
8173
8173
|
let parsed;
|
|
@@ -8180,55 +8180,54 @@ function createRPCHandler() {
|
|
|
8180
8180
|
parsed = yaml2.parse(rawContent);
|
|
8181
8181
|
}
|
|
8182
8182
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
8183
|
-
return { ok: false,
|
|
8183
|
+
return normalizeReplayResult({ ok: false, 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}.` }] });
|
|
8184
8184
|
}
|
|
8185
8185
|
} catch (e) {
|
|
8186
8186
|
const msg = e instanceof Error ? e.message : String(e);
|
|
8187
|
-
return { ok: false,
|
|
8187
|
+
return normalizeReplayResult({ ok: false, errors: [{ eventIndex: -1, error: `Failed to parse "${file}" as JSON or YAML: ${msg.split("\n")[0]}` }] });
|
|
8188
8188
|
}
|
|
8189
8189
|
const session = findSession(sessionName);
|
|
8190
8190
|
if (!session) {
|
|
8191
|
-
return { ok: false,
|
|
8191
|
+
return normalizeReplayResult({ ok: false, errors: [{ eventIndex: -1, error: `Session not found: ${sessionName}. Open a browser first: xbrowser goto <url> --session ${sessionName}` }] });
|
|
8192
8192
|
}
|
|
8193
8193
|
if (!session.page) {
|
|
8194
|
-
return { ok: false,
|
|
8194
|
+
return normalizeReplayResult({ ok: false, errors: [{ eventIndex: -1, error: "Session has no page: " + sessionName }] });
|
|
8195
8195
|
}
|
|
8196
8196
|
const isNewFormat = Array.isArray(parsed.actions);
|
|
8197
8197
|
if (isNewFormat) {
|
|
8198
8198
|
try {
|
|
8199
8199
|
const replayErrors = [];
|
|
8200
|
-
const { SessionReplayer } = await import("./session-replayer-
|
|
8200
|
+
const { SessionReplayer } = await import("./session-replayer-V4MP6M6I.js");
|
|
8201
8201
|
const replayer = new SessionReplayer({
|
|
8202
8202
|
page: session.page,
|
|
8203
8203
|
stepDelay: slowMo * 500,
|
|
8204
8204
|
onStep: (action, index, total) => {
|
|
8205
8205
|
console.log(`[replay] Step ${index + 1}/${total}: ${action.type} ${action.element?.selector || action.url || ""}`);
|
|
8206
8206
|
},
|
|
8207
|
-
onError: (action, error) => {
|
|
8207
|
+
onError: (action, error, index) => {
|
|
8208
8208
|
const msg = `[${action?.type || "unknown"}] ${error?.message || String(error)}`;
|
|
8209
8209
|
console.error("[replay] Error at step:", msg);
|
|
8210
|
-
replayErrors.push({ eventIndex:
|
|
8210
|
+
replayErrors.push({ eventIndex: index, error: msg });
|
|
8211
8211
|
}
|
|
8212
8212
|
});
|
|
8213
8213
|
if (!Array.isArray(parsed.actions) || typeof parsed.startUrl !== "string") {
|
|
8214
|
-
return { ok: false,
|
|
8214
|
+
return normalizeReplayResult({ ok: false, errors: [{ eventIndex: -1, error: "Invalid recording format: missing actions or startUrl" }] });
|
|
8215
8215
|
}
|
|
8216
8216
|
await replayer.load(parsed);
|
|
8217
8217
|
const startTime = Date.now();
|
|
8218
8218
|
const result2 = await replayer.run();
|
|
8219
8219
|
const duration = Date.now() - startTime;
|
|
8220
|
-
return {
|
|
8220
|
+
return normalizeReplayResult({
|
|
8221
8221
|
ok: result2.failed === 0,
|
|
8222
|
-
success: result2.failed === 0,
|
|
8223
8222
|
duration,
|
|
8224
8223
|
eventsPlayed: result2.success,
|
|
8225
8224
|
totalEvents: result2.success + result2.failed + result2.skipped,
|
|
8226
8225
|
errors: replayErrors
|
|
8227
|
-
};
|
|
8226
|
+
});
|
|
8228
8227
|
} catch (e) {
|
|
8229
8228
|
const msg = e instanceof Error ? e.message : String(e);
|
|
8230
8229
|
console.error("[replay] SessionReplayer error:", msg);
|
|
8231
|
-
return { ok: false,
|
|
8230
|
+
return normalizeReplayResult({ ok: false, errors: [{ eventIndex: -1, error: "Replay failed: " + msg }] });
|
|
8232
8231
|
}
|
|
8233
8232
|
}
|
|
8234
8233
|
const engine = PlaybackEngine.fromFile(session.page, file);
|
|
@@ -8242,7 +8241,21 @@ function createRPCHandler() {
|
|
|
8242
8241
|
});
|
|
8243
8242
|
}
|
|
8244
8243
|
});
|
|
8245
|
-
return { ok: result.success, ...result };
|
|
8244
|
+
return normalizeReplayResult({ ok: result.success, ...result });
|
|
8245
|
+
}
|
|
8246
|
+
function normalizeReplayResult(input) {
|
|
8247
|
+
const errors = Array.isArray(input.errors) ? input.errors : [];
|
|
8248
|
+
const eventsPlayed = typeof input.eventsPlayed === "number" ? input.eventsPlayed : 0;
|
|
8249
|
+
const totalEvents = typeof input.totalEvents === "number" ? input.totalEvents : eventsPlayed + errors.length;
|
|
8250
|
+
const ok26 = input.ok ?? errors.length === 0;
|
|
8251
|
+
return {
|
|
8252
|
+
ok: ok26,
|
|
8253
|
+
success: ok26,
|
|
8254
|
+
duration: typeof input.duration === "number" ? input.duration : 0,
|
|
8255
|
+
eventsPlayed,
|
|
8256
|
+
totalEvents,
|
|
8257
|
+
errors
|
|
8258
|
+
};
|
|
8246
8259
|
}
|
|
8247
8260
|
async function handleViewerCheckSelector(params) {
|
|
8248
8261
|
const name = params.name || "default";
|
package/dist/index.d.ts
CHANGED
|
@@ -1939,6 +1939,11 @@ declare class SessionRecorder {
|
|
|
1939
1939
|
* selector or the screenshot fails (non-critical — never blocks recording).
|
|
1940
1940
|
*
|
|
1941
1941
|
* Only captures for: click, input, change, dblclick, and their CDP counterparts.
|
|
1942
|
+
*
|
|
1943
|
+
* Retry strategy (3 attempts, 3s each):
|
|
1944
|
+
* 1. element screenshot via locator
|
|
1945
|
+
* 2. retry element screenshot (element may have been animating/detached)
|
|
1946
|
+
* 3. fallback to viewport screenshot (so we always get *something*)
|
|
1942
1947
|
*/
|
|
1943
1948
|
private captureElementScreenshot;
|
|
1944
1949
|
/**
|
package/dist/index.js
CHANGED
|
@@ -61,7 +61,7 @@ import {
|
|
|
61
61
|
} from "./chunk-ANVL2ID2.js";
|
|
62
62
|
import {
|
|
63
63
|
SessionRecorder
|
|
64
|
-
} from "./chunk-
|
|
64
|
+
} from "./chunk-C3LIGO4V.js";
|
|
65
65
|
import {
|
|
66
66
|
addKnownIssue,
|
|
67
67
|
getKnowledgePath,
|
|
@@ -86,8 +86,8 @@ import {
|
|
|
86
86
|
resolveLaunchOpts,
|
|
87
87
|
saveSessionDiskMeta,
|
|
88
88
|
setActivePage
|
|
89
|
-
} from "./chunk-
|
|
90
|
-
import "./chunk-
|
|
89
|
+
} from "./chunk-PBDID7SE.js";
|
|
90
|
+
import "./chunk-GM3HWQ5U.js";
|
|
91
91
|
import "./chunk-TNEN6VQ2.js";
|
|
92
92
|
import {
|
|
93
93
|
errMsg
|
|
@@ -7406,7 +7406,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
7406
7406
|
}
|
|
7407
7407
|
let targetPageOverride = null;
|
|
7408
7408
|
if (_target && extraOpts?.cdpEndpoint) {
|
|
7409
|
-
const { findTargetPage } = await import("./browser-
|
|
7409
|
+
const { findTargetPage } = await import("./browser-SJWPJFN7.js");
|
|
7410
7410
|
targetPageOverride = await findTargetPage(extraOpts.cdpEndpoint, _target);
|
|
7411
7411
|
if (!targetPageOverride) {
|
|
7412
7412
|
return errorResult(`Target "${_target}" not found. Use 'xbrowser targets --cdp ${extraOpts.cdpEndpoint}' to list available pages.`);
|
|
@@ -10711,6 +10711,50 @@ async function infoFromMarketplacePlugin(slug, loader) {
|
|
|
10711
10711
|
}
|
|
10712
10712
|
return null;
|
|
10713
10713
|
}
|
|
10714
|
+
function normalizePluginName(name) {
|
|
10715
|
+
return name.replace(/^@[^/]+\//, "").replace(/^xbrowser-plugin-/, "").toLowerCase();
|
|
10716
|
+
}
|
|
10717
|
+
async function searchLocalPlugins(query) {
|
|
10718
|
+
let installed;
|
|
10719
|
+
try {
|
|
10720
|
+
installed = await new PluginInstaller().list();
|
|
10721
|
+
} catch {
|
|
10722
|
+
return [];
|
|
10723
|
+
}
|
|
10724
|
+
if (!installed || installed.length === 0) return [];
|
|
10725
|
+
const q = normalizePluginName(query);
|
|
10726
|
+
if (!q) return [];
|
|
10727
|
+
const results = [];
|
|
10728
|
+
for (const p of installed) {
|
|
10729
|
+
const plugin = p;
|
|
10730
|
+
const name = String(plugin.name ?? "");
|
|
10731
|
+
const norm = normalizePluginName(name);
|
|
10732
|
+
if (norm === q) {
|
|
10733
|
+
results.push({ plugin, rank: 0 });
|
|
10734
|
+
} else if (norm.startsWith(q) || q.startsWith(norm)) {
|
|
10735
|
+
results.push({ plugin, rank: 1 });
|
|
10736
|
+
} else if (norm.includes(q) || q.includes(norm)) {
|
|
10737
|
+
results.push({ plugin, rank: 2 });
|
|
10738
|
+
}
|
|
10739
|
+
}
|
|
10740
|
+
results.sort((a, b) => a.rank - b.rank);
|
|
10741
|
+
const runtimeInfo = await buildRuntimePluginInfo().catch(() => /* @__PURE__ */ new Map());
|
|
10742
|
+
return results.map(({ plugin }) => {
|
|
10743
|
+
const name = String(plugin.name ?? "");
|
|
10744
|
+
const metadata = plugin.metadata;
|
|
10745
|
+
const staticCommands = metadata?.commands;
|
|
10746
|
+
const rt = runtimeInfo.get(name);
|
|
10747
|
+
const commands = rt?.commands || staticCommands;
|
|
10748
|
+
return {
|
|
10749
|
+
name: "@xbrowser/" + name,
|
|
10750
|
+
slug: name,
|
|
10751
|
+
description: metadata?.description,
|
|
10752
|
+
version: metadata?.version,
|
|
10753
|
+
commands,
|
|
10754
|
+
source: "local"
|
|
10755
|
+
};
|
|
10756
|
+
});
|
|
10757
|
+
}
|
|
10714
10758
|
async function handleSearch(args, options, mode) {
|
|
10715
10759
|
const query = args[0] || "";
|
|
10716
10760
|
applyRegistryOverride(options);
|
|
@@ -10718,6 +10762,8 @@ async function handleSearch(args, options, mode) {
|
|
|
10718
10762
|
const searchLimit = options.limit ? Number(options.limit) : 20;
|
|
10719
10763
|
const searchOpts = { query, tag: options.tag, site: options.site, limit: searchLimit };
|
|
10720
10764
|
const results = [];
|
|
10765
|
+
const localResults = await searchLocalPlugins(query);
|
|
10766
|
+
results.push(...localResults);
|
|
10721
10767
|
const loader = await getPluginLoader();
|
|
10722
10768
|
const pluginResults = await searchFromMarketplacePlugin2(searchOpts, loader);
|
|
10723
10769
|
results.push(...pluginResults);
|
|
@@ -10730,15 +10776,24 @@ async function handleSearch(args, options, mode) {
|
|
|
10730
10776
|
} catch {
|
|
10731
10777
|
}
|
|
10732
10778
|
}
|
|
10779
|
+
const seen = /* @__PURE__ */ new Set();
|
|
10780
|
+
const deduped = [];
|
|
10781
|
+
for (const r of results) {
|
|
10782
|
+
const key = normalizePluginName(String(r.name ?? ""));
|
|
10783
|
+
if (key && !seen.has(key)) {
|
|
10784
|
+
seen.add(key);
|
|
10785
|
+
deduped.push(r);
|
|
10786
|
+
}
|
|
10787
|
+
}
|
|
10733
10788
|
if (mode === "json") {
|
|
10734
|
-
outputEnvelope({ success: true, data: { results, total:
|
|
10789
|
+
outputEnvelope({ success: true, data: { results: deduped, total: deduped.length } }, { command: "plugin search" }, mode);
|
|
10735
10790
|
} else {
|
|
10736
|
-
if (
|
|
10791
|
+
if (deduped.length === 0) {
|
|
10737
10792
|
console.log("No plugins found");
|
|
10738
10793
|
return;
|
|
10739
10794
|
}
|
|
10740
|
-
for (const r of
|
|
10741
|
-
const src = r.source === "marketplace" ? "[marketplace]" : "[npm]";
|
|
10795
|
+
for (const r of deduped) {
|
|
10796
|
+
const src = r.source === "marketplace" ? "[marketplace]" : r.source === "local" ? "[local]" : "[npm]";
|
|
10742
10797
|
const slug = r.slug ? ` (${r.slug})` : "";
|
|
10743
10798
|
console.log(` ${src} ${r.name}${slug}`);
|
|
10744
10799
|
if (r.description) console.log(` ${r.description}`);
|
|
@@ -10746,7 +10801,7 @@ async function handleSearch(args, options, mode) {
|
|
|
10746
10801
|
if (r.downloads) console.log(` Downloads: ${r.downloads}`);
|
|
10747
10802
|
console.log("");
|
|
10748
10803
|
}
|
|
10749
|
-
console.log(`Total: ${
|
|
10804
|
+
console.log(`Total: ${deduped.length} plugins`);
|
|
10750
10805
|
}
|
|
10751
10806
|
}
|
|
10752
10807
|
async function handlePluginInfo(args, options, mode) {
|
|
@@ -11448,7 +11503,7 @@ async function handleFilter(args, _mode, options) {
|
|
|
11448
11503
|
}
|
|
11449
11504
|
}
|
|
11450
11505
|
async function handleGeneratePlugin(sessionName, pluginName, outputDir) {
|
|
11451
|
-
const { SessionRecorder: SessionRecorder2 } = await import("./session-recorder-
|
|
11506
|
+
const { SessionRecorder: SessionRecorder2 } = await import("./session-recorder-AG6CH6EI.js");
|
|
11452
11507
|
const { readSiteKnowledge: readSiteKnowledge2, toMarkdown } = await import("./site-knowledge-SYC6VCDB.js");
|
|
11453
11508
|
const { mkdirSync: mkdirSync11, writeFileSync: writeFileSync13 } = await import("fs");
|
|
11454
11509
|
const { join: join14 } = await import("path");
|
|
@@ -13425,7 +13480,7 @@ Run "xbrowser ${command} ${subCommand} --help" to see available parameters.`
|
|
|
13425
13480
|
const targetPage = pages[cmdTabIndex];
|
|
13426
13481
|
await targetPage.bringToFront().catch(() => {
|
|
13427
13482
|
});
|
|
13428
|
-
const { setActivePage: setActivePage2 } = await import("./browser-
|
|
13483
|
+
const { setActivePage: setActivePage2 } = await import("./browser-SJWPJFN7.js");
|
|
13429
13484
|
setActivePage2(session, targetPage);
|
|
13430
13485
|
}
|
|
13431
13486
|
}
|
|
@@ -16488,7 +16543,7 @@ var DataCollector = class {
|
|
|
16488
16543
|
return results;
|
|
16489
16544
|
}
|
|
16490
16545
|
async createBrowserContext() {
|
|
16491
|
-
const { launch } = await import("./cdp-driver-
|
|
16546
|
+
const { launch } = await import("./cdp-driver-PUZLDSQU.js");
|
|
16492
16547
|
const { browser } = await launch({
|
|
16493
16548
|
headless: true,
|
|
16494
16549
|
args: ["--no-sandbox", "--disable-setuid-sandbox"]
|
|
@@ -31,7 +31,7 @@ var SessionReplayer = class {
|
|
|
31
31
|
if (this.opts.page) {
|
|
32
32
|
this.page = this.opts.page;
|
|
33
33
|
} else if (this.opts.cdpUrl) {
|
|
34
|
-
const { launch } = await import("./cdp-driver-
|
|
34
|
+
const { launch } = await import("./cdp-driver-PUZLDSQU.js");
|
|
35
35
|
const { browser } = await launch({ cdpEndpoint: this.opts.cdpUrl });
|
|
36
36
|
let contexts = browser.contexts();
|
|
37
37
|
for (let i = 0; i < 10 && contexts.length === 0; i++) {
|
|
@@ -65,7 +65,7 @@ var SessionReplayer = class {
|
|
|
65
65
|
success++;
|
|
66
66
|
} catch (e) {
|
|
67
67
|
const err = e instanceof Error ? e : new Error(String(e));
|
|
68
|
-
this.opts.onError?.(action, err);
|
|
68
|
+
this.opts.onError?.(action, err, i);
|
|
69
69
|
failed++;
|
|
70
70
|
}
|
|
71
71
|
if (i < actions.length - 1) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xbrowser/cli",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.8",
|
|
4
4
|
"description": "Browser automation CLI for web scraping, headless browsing, SEO analysis, and AI agent workflows. A command-line alternative to Playwright, Puppeteer, and Selenium.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|