@xbrowser/cli 1.21.0 → 1.23.0
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/README.md +21 -3
- package/dist/{browser-CNBSH53B.js → browser-BVRFSUXU.js} +3 -2
- package/dist/{browser-C2VEOJC2.js → browser-DSYUAE7H.js} +5 -4
- package/dist/{browser-VZOOFLAH.js → browser-Q67BJTI2.js} +4 -3
- package/dist/{cdp-driver-KFX4A6WR.js → cdp-driver-DC7EIUG5.js} +4 -3
- package/dist/{cdp-driver-7Z6ZAT5E.js → cdp-driver-T7D6H2RW.js} +335 -546
- package/dist/{cdp-driver-26LGCHXV.js → cdp-driver-WWO2UZDR.js} +3 -2
- package/dist/{chunk-Z5RTK4GW.js → chunk-2Q3GQRUP.js} +2 -2
- package/dist/{chunk-CLYZPSDR.js → chunk-4OU37CXP.js} +354 -576
- package/dist/{chunk-HWWG2ISJ.js → chunk-7OP2ISEY.js} +353 -575
- package/dist/{chunk-PIWNMC36.js → chunk-A3MORKYR.js} +1 -1
- package/dist/{chunk-3FWLW7FS.js → chunk-A7NEA4PA.js} +39 -1
- package/dist/{chunk-OMU63E6J.js → chunk-FBQZ3PVD.js} +5 -8
- package/dist/chunk-GYB5BOSP.js +602 -0
- package/dist/{chunk-ESJYANQO.js → chunk-IFKJO2UR.js} +360 -582
- package/dist/{chunk-NODRQGOK.js → chunk-IR6C24P7.js} +97 -0
- package/dist/{chunk-TEXCXIBW.js → chunk-LPW6QS4M.js} +5 -8
- package/dist/{chunk-3KZ34AUC.js → chunk-MYH6FIBZ.js} +2 -1
- package/dist/{chunk-AVSTNODR.js → chunk-T4WTKBWI.js} +2 -2
- package/dist/{chunk-HBHOKZPN.js → chunk-V6KGBU5J.js} +97 -0
- package/dist/{chunk-IWVG4XYZ.js → chunk-VCVDILH6.js} +2 -1
- package/dist/{chunk-NKW4A74J.js → chunk-XTUAUFL3.js} +5 -8
- package/dist/cli.js +497 -252
- package/dist/daemon-main.js +25 -19
- package/dist/{human-interaction-Y5IDH6LD.js → human-interaction-57VYIR56.js} +1 -1
- package/dist/{human-interaction-ZUTR5AC2.js → human-interaction-67K43B4O.js} +1 -1
- package/dist/{human-interaction-4YR2N6R6.js → human-interaction-MXST5ZQM.js} +1 -1
- package/dist/index.d.ts +52 -4
- package/dist/index.js +500 -256
- package/dist/keep-awake-M4KJS4B4.js +29 -0
- package/dist/{launcher-44STYRJC.js → launcher-IK2FKW6T.js} +1 -1
- package/dist/{launcher-EXJHHAUL.js → launcher-LUOMMGZC.js} +1 -1
- package/dist/{plugin-singleton-FCNDN3FX.js → plugin-singleton-GJO7EZXB.js} +1 -1
- package/dist/{recovery-NXC35EQN.js → recovery-6RI2FE3E.js} +1 -1
- package/dist/{recovery-MDWAVXE4.js → recovery-LGY25LIM.js} +1 -1
- package/dist/{recovery-L5GLDX4O.js → recovery-LMJW537X.js} +1 -1
- package/dist/{session-recorder-SLDBENVF.js → session-recorder-563ISSJK.js} +1 -1
- package/dist/{session-recorder-3BEVWHOK.js → session-recorder-GEZVQBKW.js} +1 -1
- package/dist/session-replayer-N665T3G5.js +932 -0
- package/dist/stealth-2AA2FSNN.js +29 -0
- package/dist/stealth-JOXGV34D.js +29 -0
- package/package.json +11 -7
- package/dist/session-replayer-EVD4HZBB.js +0 -413
package/dist/index.js
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
|
+
import {
|
|
2
|
+
extractAndSave,
|
|
3
|
+
extractRecording,
|
|
4
|
+
printExtractSummary
|
|
5
|
+
} from "./chunk-X46HDAOT.js";
|
|
1
6
|
import {
|
|
2
7
|
filterRecording,
|
|
3
8
|
parseExcludeTypes
|
|
4
9
|
} from "./chunk-ANVL2ID2.js";
|
|
10
|
+
import {
|
|
11
|
+
SessionRecorder
|
|
12
|
+
} from "./chunk-IR6C24P7.js";
|
|
5
13
|
import {
|
|
6
14
|
closeAllSessions,
|
|
7
15
|
closeEphemeralContext,
|
|
@@ -18,7 +26,7 @@ import {
|
|
|
18
26
|
resolveLaunchOpts,
|
|
19
27
|
saveSessionDiskMeta,
|
|
20
28
|
setActivePage
|
|
21
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-2Q3GQRUP.js";
|
|
22
30
|
import {
|
|
23
31
|
detectAntiBot,
|
|
24
32
|
formatDetectionMessage
|
|
@@ -64,7 +72,7 @@ import {
|
|
|
64
72
|
resolveNpmPackageWithFallback,
|
|
65
73
|
resolveScreenshotsDir,
|
|
66
74
|
setConfigValue
|
|
67
|
-
} from "./chunk-
|
|
75
|
+
} from "./chunk-XTUAUFL3.js";
|
|
68
76
|
import {
|
|
69
77
|
getDaemonConfig,
|
|
70
78
|
getDaemonProcessStatus,
|
|
@@ -77,17 +85,13 @@ import {
|
|
|
77
85
|
generateJSScript,
|
|
78
86
|
generatePythonScript
|
|
79
87
|
} from "./chunk-ETCO4SNK.js";
|
|
80
|
-
import
|
|
81
|
-
extractAndSave,
|
|
82
|
-
extractRecording,
|
|
83
|
-
printExtractSummary
|
|
84
|
-
} from "./chunk-X46HDAOT.js";
|
|
88
|
+
import "./chunk-7OP2ISEY.js";
|
|
85
89
|
import {
|
|
86
90
|
rand,
|
|
87
91
|
sleep,
|
|
88
92
|
wheelDelta
|
|
89
|
-
} from "./chunk-
|
|
90
|
-
import "./chunk-
|
|
93
|
+
} from "./chunk-GYB5BOSP.js";
|
|
94
|
+
import "./chunk-MYH6FIBZ.js";
|
|
91
95
|
import {
|
|
92
96
|
errMsg
|
|
93
97
|
} from "./chunk-GDKLH7ZY.js";
|
|
@@ -107,9 +111,6 @@ import {
|
|
|
107
111
|
networkAnomalyRule,
|
|
108
112
|
pageLifecycleRule
|
|
109
113
|
} from "./chunk-75DNUI6M.js";
|
|
110
|
-
import {
|
|
111
|
-
SessionRecorder
|
|
112
|
-
} from "./chunk-NODRQGOK.js";
|
|
113
114
|
import {
|
|
114
115
|
addKnownIssue,
|
|
115
116
|
getKnowledgePath,
|
|
@@ -7402,7 +7403,7 @@ function attachWaitForHuman(ctx, getOrCreateWSServer) {
|
|
|
7402
7403
|
if (!ctx.page) {
|
|
7403
7404
|
throw new Error("waitForHuman requires an active page");
|
|
7404
7405
|
}
|
|
7405
|
-
const { HumanInteractionManager: HumanInteractionManager2 } = await import("./human-interaction-
|
|
7406
|
+
const { HumanInteractionManager: HumanInteractionManager2 } = await import("./human-interaction-57VYIR56.js");
|
|
7406
7407
|
const wsServer2 = await getOrCreateWSServer(ctx.browserContext);
|
|
7407
7408
|
const manager = new HumanInteractionManager2(wsServer2, ctx.page);
|
|
7408
7409
|
return manager.waitForHuman(options);
|
|
@@ -7518,7 +7519,7 @@ import { join as join7 } from "path";
|
|
|
7518
7519
|
import { execSync } from "child_process";
|
|
7519
7520
|
var SHARED_PLUGIN_DEPENDENCIES = {
|
|
7520
7521
|
"zod": "^3.24.0",
|
|
7521
|
-
"@dyyz1993/xcli-core": "^0.
|
|
7522
|
+
"@dyyz1993/xcli-core": "^0.19.0"
|
|
7522
7523
|
};
|
|
7523
7524
|
function ensurePluginDependencies(pluginsDir) {
|
|
7524
7525
|
const zodPath = join7(pluginsDir, "node_modules", "zod");
|
|
@@ -8599,7 +8600,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
8599
8600
|
}
|
|
8600
8601
|
let targetPageOverride = null;
|
|
8601
8602
|
if (_target && extraOpts?.cdpEndpoint) {
|
|
8602
|
-
const { findTargetPage } = await import("./browser-
|
|
8603
|
+
const { findTargetPage } = await import("./browser-Q67BJTI2.js");
|
|
8603
8604
|
targetPageOverride = await findTargetPage(extraOpts.cdpEndpoint, _target);
|
|
8604
8605
|
if (!targetPageOverride) {
|
|
8605
8606
|
return errorResult(`Target "${_target}" not found. Use 'xbrowser targets --cdp ${extraOpts.cdpEndpoint}' to list available pages.`);
|
|
@@ -8816,7 +8817,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
8816
8817
|
const errorMessage = errMsg(err);
|
|
8817
8818
|
if (session?.page && process.env.XBROWSER_RECOVERY && !extraOpts?._recoveryAttempted) {
|
|
8818
8819
|
try {
|
|
8819
|
-
const { attemptRecovery } = await import("./recovery-
|
|
8820
|
+
const { attemptRecovery } = await import("./recovery-LMJW537X.js");
|
|
8820
8821
|
const recovery = await attemptRecovery(
|
|
8821
8822
|
session.page,
|
|
8822
8823
|
sessionName,
|
|
@@ -9531,13 +9532,39 @@ async function installFromNpm(packageName, name, targetDir) {
|
|
|
9531
9532
|
import { existsSync as existsSync9, readFileSync as readFileSync7, writeFileSync as writeFileSync7, rmSync as rmSync3, cpSync as cpSync3 } from "fs";
|
|
9532
9533
|
import { resolve as resolve5, join as join10 } from "path";
|
|
9533
9534
|
import { tmpdir as tmpdir2 } from "os";
|
|
9534
|
-
import {
|
|
9535
|
+
import { execFile as execFile2 } from "child_process";
|
|
9535
9536
|
import { verifyPlugin as verifyPlugin3, safeCleanup as safeCleanup3 } from "@dyyz1993/xcli-core";
|
|
9537
|
+
var GIT_URL_RE = /^(?:https|git|git\+https|git\+ssh|ssh):\/\/[^\s"'`\\;<>()&]+$/i;
|
|
9538
|
+
var GIT_SCP_RE = /^git@[\w.-]+:[\w./~+-]+$/;
|
|
9539
|
+
function isValidGitUrl(url) {
|
|
9540
|
+
if (typeof url !== "string" || url.length === 0) return false;
|
|
9541
|
+
if (url.startsWith("-")) return false;
|
|
9542
|
+
if (/[\r\n\0]/.test(url)) return false;
|
|
9543
|
+
return GIT_URL_RE.test(url) || GIT_SCP_RE.test(url);
|
|
9544
|
+
}
|
|
9545
|
+
function execGit(args) {
|
|
9546
|
+
return new Promise((resolve10, reject) => {
|
|
9547
|
+
execFile2("git", args, (err, _stdout, stderr) => {
|
|
9548
|
+
if (err) {
|
|
9549
|
+
const stderrText = typeof stderr === "string" ? stderr : stderr?.toString("utf-8") ?? "";
|
|
9550
|
+
const detail = stderrText.trim() || err.message;
|
|
9551
|
+
reject(new Error(`git clone failed: ${detail}`));
|
|
9552
|
+
} else {
|
|
9553
|
+
resolve10();
|
|
9554
|
+
}
|
|
9555
|
+
});
|
|
9556
|
+
});
|
|
9557
|
+
}
|
|
9536
9558
|
async function installFromGit(gitUrl, name, targetDir) {
|
|
9559
|
+
if (!isValidGitUrl(gitUrl)) {
|
|
9560
|
+
throw new Error(
|
|
9561
|
+
`Invalid git URL: ${JSON.stringify(gitUrl).slice(0, 120)} \u2014 allowed forms: https://, git://, git+https://, git+ssh://, ssh://, git@host:path`
|
|
9562
|
+
);
|
|
9563
|
+
}
|
|
9537
9564
|
const tmpDir = join10(tmpdir2(), `xbrowser-git-${Date.now()}`);
|
|
9538
9565
|
let warnings = [];
|
|
9539
9566
|
try {
|
|
9540
|
-
|
|
9567
|
+
await execGit(["clone", "--depth", "1", "--", gitUrl, tmpDir]);
|
|
9541
9568
|
const verify = verifyPlugin3(tmpDir, { metadataField: "xbrowser" });
|
|
9542
9569
|
warnings = verify.warnings ?? [];
|
|
9543
9570
|
if (!verify.valid) {
|
|
@@ -11377,7 +11404,7 @@ async function handleBrowserCommand(command, args, options, sessionName, mode, c
|
|
|
11377
11404
|
}
|
|
11378
11405
|
outputResult({ command: cmdDef.name, description: cmdDef.description, scope: cmdDef.scope, parameters: paramsList }, mode);
|
|
11379
11406
|
} else {
|
|
11380
|
-
|
|
11407
|
+
process.stdout.write(helpGenerator.generate(cmdDef, { color: true, emoji: false }));
|
|
11381
11408
|
}
|
|
11382
11409
|
} else {
|
|
11383
11410
|
outputError(`Unknown command: ${command}`);
|
|
@@ -11778,7 +11805,7 @@ async function handleBrowserCommand(command, args, options, sessionName, mode, c
|
|
|
11778
11805
|
if (isEmptyResult) {
|
|
11779
11806
|
const hint = cdpEndpoint ? `\u53EF\u80FD\u672A\u8FDE\u63A5\u5230\u6D4F\u89C8\u5668\u3002\u8BF7\u786E\u8BA4 ${cdpEndpoint} \u4E0A\u6709 Chrome \u8FD0\u884C\uFF08--remote-debugging-port\uFF09\u3002` : "\u53EF\u80FD\u672A\u8FDE\u63A5\u5230\u6D4F\u89C8\u5668\u3002\u8BF7\u4F7F\u7528 --cdp <endpoint> \u8FDE\u63A5\uFF0C\u6216\u5B89\u88C5 cdp-tunnel \u590D\u7528\u5DF2\u6709 Chrome\u3002";
|
|
11780
11807
|
outputResult(result.data, mode);
|
|
11781
|
-
|
|
11808
|
+
outputError(`
|
|
11782
11809
|
\u26A0\uFE0F ${hint}`);
|
|
11783
11810
|
process.exit(1);
|
|
11784
11811
|
} else {
|
|
@@ -11795,8 +11822,9 @@ async function handleBrowserCommand(command, args, options, sessionName, mode, c
|
|
|
11795
11822
|
try {
|
|
11796
11823
|
mkdirSync11(dirname7(outputFile), { recursive: true });
|
|
11797
11824
|
writeFileSync13(outputFile, content, "utf-8");
|
|
11798
|
-
|
|
11799
|
-
\u{1F4C4} Written to ${outputFile}
|
|
11825
|
+
process.stdout.write(`
|
|
11826
|
+
\u{1F4C4} Written to ${outputFile}
|
|
11827
|
+
`);
|
|
11800
11828
|
} catch (err) {
|
|
11801
11829
|
outputError(`Failed to write --output "${outputFile}": ${err instanceof Error ? err.message : String(err)}`);
|
|
11802
11830
|
}
|
|
@@ -11894,7 +11922,7 @@ async function handleSession(args, options, mode, _cdpEndpoint) {
|
|
|
11894
11922
|
break;
|
|
11895
11923
|
}
|
|
11896
11924
|
default:
|
|
11897
|
-
|
|
11925
|
+
process.stdout.write(handleSessionHelp() + "\n");
|
|
11898
11926
|
}
|
|
11899
11927
|
}
|
|
11900
11928
|
|
|
@@ -12068,19 +12096,24 @@ async function handleSearch(args, options, mode) {
|
|
|
12068
12096
|
outputEnvelope({ success: true, data: { results: deduped, total: deduped.length } }, { command: "plugin search" }, mode);
|
|
12069
12097
|
} else {
|
|
12070
12098
|
if (deduped.length === 0) {
|
|
12071
|
-
|
|
12099
|
+
process.stdout.write("No plugins found\n");
|
|
12072
12100
|
return;
|
|
12073
12101
|
}
|
|
12074
12102
|
for (const r of deduped) {
|
|
12075
12103
|
const src = r.source === "marketplace" ? "[marketplace]" : r.source === "local" ? "[local]" : "[npm]";
|
|
12076
12104
|
const slug = r.slug ? ` (${r.slug})` : "";
|
|
12077
|
-
|
|
12078
|
-
|
|
12079
|
-
if (r.
|
|
12080
|
-
|
|
12081
|
-
|
|
12105
|
+
process.stdout.write(` ${src} ${r.name}${slug}
|
|
12106
|
+
`);
|
|
12107
|
+
if (r.description) process.stdout.write(` ${r.description}
|
|
12108
|
+
`);
|
|
12109
|
+
if (r.version) process.stdout.write(` Version: ${r.version}
|
|
12110
|
+
`);
|
|
12111
|
+
if (r.downloads) process.stdout.write(` Downloads: ${r.downloads}
|
|
12112
|
+
`);
|
|
12113
|
+
process.stdout.write("\n");
|
|
12082
12114
|
}
|
|
12083
|
-
|
|
12115
|
+
process.stdout.write(`Total: ${deduped.length} plugins
|
|
12116
|
+
`);
|
|
12084
12117
|
}
|
|
12085
12118
|
}
|
|
12086
12119
|
async function handlePluginInfo(args, options, mode) {
|
|
@@ -12097,14 +12130,22 @@ async function handlePluginInfo(args, options, mode) {
|
|
|
12097
12130
|
outputEnvelope({ success: true, data: { source: "marketplace", ...d } }, { command: "plugin info" }, mode);
|
|
12098
12131
|
return;
|
|
12099
12132
|
}
|
|
12100
|
-
|
|
12101
|
-
|
|
12102
|
-
|
|
12103
|
-
|
|
12104
|
-
|
|
12105
|
-
|
|
12106
|
-
|
|
12107
|
-
|
|
12133
|
+
process.stdout.write(`\u540D\u79F0: ${d.name || ""}
|
|
12134
|
+
`);
|
|
12135
|
+
process.stdout.write(`\u7248\u672C: ${d.version || ""}
|
|
12136
|
+
`);
|
|
12137
|
+
process.stdout.write(`\u63CF\u8FF0: ${d.description || ""}
|
|
12138
|
+
`);
|
|
12139
|
+
process.stdout.write(`\u4F5C\u8005: ${d.author || ""}
|
|
12140
|
+
`);
|
|
12141
|
+
process.stdout.write(`\u547D\u4EE4: ${(d.commands || []).join(", ")}
|
|
12142
|
+
`);
|
|
12143
|
+
process.stdout.write(`\u4E0B\u8F7D\u91CF: ${d.downloads || 0}
|
|
12144
|
+
`);
|
|
12145
|
+
process.stdout.write(`\u6807\u7B7E: ${(d.tags || []).join(", ")}
|
|
12146
|
+
`);
|
|
12147
|
+
process.stdout.write(`\u7F51\u7AD9: ${(d.sites || []).join(", ")}
|
|
12148
|
+
`);
|
|
12108
12149
|
return;
|
|
12109
12150
|
}
|
|
12110
12151
|
} catch {
|
|
@@ -12123,19 +12164,25 @@ async function handlePluginInfo(args, options, mode) {
|
|
|
12123
12164
|
outputEnvelope({ success: true, data: { source: "npm", name: pkg.name, version: latest, description: pkg.description } }, { command: "plugin info" }, mode);
|
|
12124
12165
|
return;
|
|
12125
12166
|
}
|
|
12126
|
-
|
|
12127
|
-
|
|
12128
|
-
|
|
12167
|
+
process.stdout.write(`\u540D\u79F0: ${pkg.name || ""}
|
|
12168
|
+
`);
|
|
12169
|
+
process.stdout.write(`\u7248\u672C: ${latest}
|
|
12170
|
+
`);
|
|
12171
|
+
process.stdout.write(`\u63CF\u8FF0: ${pkg.description || ""}
|
|
12172
|
+
`);
|
|
12129
12173
|
const author = pkg.author;
|
|
12130
|
-
|
|
12131
|
-
|
|
12132
|
-
|
|
12174
|
+
process.stdout.write(`\u4F5C\u8005: ${typeof author === "string" ? author : author?.name || ""}
|
|
12175
|
+
`);
|
|
12176
|
+
process.stdout.write(`\u5173\u952E\u8BCD: ${(pkg.keywords || []).join(", ")}
|
|
12177
|
+
`);
|
|
12178
|
+
process.stdout.write(`\u8BB8\u53EF\u8BC1: ${pkg.license || ""}
|
|
12179
|
+
`);
|
|
12133
12180
|
return;
|
|
12134
12181
|
}
|
|
12135
12182
|
}
|
|
12136
|
-
|
|
12183
|
+
outputError(`\u63D2\u4EF6 '${slug}' \u672A\u627E\u5230`);
|
|
12137
12184
|
} catch (err) {
|
|
12138
|
-
|
|
12185
|
+
outputError("\u67E5\u8BE2\u5931\u8D25: " + errMsg(err));
|
|
12139
12186
|
}
|
|
12140
12187
|
}
|
|
12141
12188
|
async function handlePluginSchema(args, mode) {
|
|
@@ -12159,32 +12206,39 @@ async function handlePluginSchema(args, mode) {
|
|
|
12159
12206
|
}
|
|
12160
12207
|
}
|
|
12161
12208
|
function printPluginContract(contract) {
|
|
12162
|
-
|
|
12163
|
-
|
|
12164
|
-
|
|
12209
|
+
process.stdout.write(`${contract.plugin.name} contract v${contract.version}
|
|
12210
|
+
`);
|
|
12211
|
+
if (contract.plugin.description) process.stdout.write(contract.plugin.description + "\n");
|
|
12212
|
+
process.stdout.write("\n");
|
|
12165
12213
|
for (const command of contract.commands) {
|
|
12166
12214
|
printCommandContract(contract.plugin.name, command);
|
|
12167
12215
|
}
|
|
12168
12216
|
}
|
|
12169
12217
|
function printCommandContract(pluginName, command) {
|
|
12170
|
-
|
|
12171
|
-
|
|
12172
|
-
|
|
12218
|
+
process.stdout.write(`${pluginName} ${command.name}
|
|
12219
|
+
`);
|
|
12220
|
+
if (command.description) process.stdout.write(` ${command.description}
|
|
12221
|
+
`);
|
|
12222
|
+
process.stdout.write(` scope: ${command.scope}
|
|
12223
|
+
`);
|
|
12173
12224
|
if (command.capabilities.length > 0) {
|
|
12174
|
-
|
|
12225
|
+
process.stdout.write(` capabilities: ${command.capabilities.join(", ")}
|
|
12226
|
+
`);
|
|
12175
12227
|
}
|
|
12176
12228
|
if (command.positional.length > 0) {
|
|
12177
|
-
|
|
12229
|
+
process.stdout.write(` positional: ${command.positional.join(", ")}
|
|
12230
|
+
`);
|
|
12178
12231
|
}
|
|
12179
12232
|
if (command.form.fields.length > 0) {
|
|
12180
|
-
|
|
12233
|
+
process.stdout.write(" fields:\n");
|
|
12181
12234
|
for (const field of command.form.fields) {
|
|
12182
12235
|
const required = field.required ? "required" : "optional";
|
|
12183
12236
|
const choices = field.enum ? ` [${field.enum.join("|")}]` : "";
|
|
12184
|
-
|
|
12237
|
+
process.stdout.write(` --${field.name}: ${field.type}/${field.widget} ${required}${choices}
|
|
12238
|
+
`);
|
|
12185
12239
|
}
|
|
12186
12240
|
}
|
|
12187
|
-
|
|
12241
|
+
process.stdout.write("\n");
|
|
12188
12242
|
}
|
|
12189
12243
|
async function handlePlugin(args, options, mode) {
|
|
12190
12244
|
const sub = args[0];
|
|
@@ -12273,24 +12327,28 @@ async function handlePlugin(args, options, mode) {
|
|
|
12273
12327
|
outputEnvelope({ success: true, data: { plugins: enrichedPlugins } }, { command: "plugin list" }, mode);
|
|
12274
12328
|
} else {
|
|
12275
12329
|
if (enrichedPlugins.length === 0) {
|
|
12276
|
-
|
|
12330
|
+
process.stdout.write("No plugins installed\n");
|
|
12277
12331
|
return;
|
|
12278
12332
|
}
|
|
12279
12333
|
for (const p of enrichedPlugins) {
|
|
12280
12334
|
const loginTag = p.hasLogin ? p.loggedIn ? " [logged in]" : " [need login]" : "";
|
|
12281
12335
|
if (p.version && p.description) {
|
|
12282
|
-
|
|
12336
|
+
process.stdout.write(`${p.name} (${p.version}) - ${p.description}${loginTag}
|
|
12337
|
+
`);
|
|
12283
12338
|
} else {
|
|
12284
|
-
|
|
12339
|
+
process.stdout.write(`${p.name}${loginTag}
|
|
12340
|
+
`);
|
|
12285
12341
|
}
|
|
12286
12342
|
if (p.commands && p.commands.length > 0) {
|
|
12287
|
-
|
|
12343
|
+
process.stdout.write(` ${p.commands.join(", ")}
|
|
12344
|
+
`);
|
|
12288
12345
|
}
|
|
12289
12346
|
if (p.requiresLoginCommands.length > 0) {
|
|
12290
|
-
|
|
12347
|
+
process.stdout.write(` requires login: ${p.requiresLoginCommands.join(", ")}
|
|
12348
|
+
`);
|
|
12291
12349
|
}
|
|
12292
12350
|
}
|
|
12293
|
-
|
|
12351
|
+
process.stdout.write(`
|
|
12294
12352
|
Total: ${enrichedPlugins.length} plugins`);
|
|
12295
12353
|
}
|
|
12296
12354
|
break;
|
|
@@ -12327,7 +12385,7 @@ See docs/plugin-guide.md for the publishing workflow.`
|
|
|
12327
12385
|
);
|
|
12328
12386
|
break;
|
|
12329
12387
|
default:
|
|
12330
|
-
|
|
12388
|
+
process.stdout.write(handlePluginHelp() + "\n");
|
|
12331
12389
|
}
|
|
12332
12390
|
}
|
|
12333
12391
|
function handleCreate(args, options) {
|
|
@@ -12361,7 +12419,7 @@ function handleDaemon(args, options, mode) {
|
|
|
12361
12419
|
break;
|
|
12362
12420
|
}
|
|
12363
12421
|
default:
|
|
12364
|
-
|
|
12422
|
+
process.stdout.write("Daemon starts automatically. No manual action needed.\n");
|
|
12365
12423
|
}
|
|
12366
12424
|
}
|
|
12367
12425
|
|
|
@@ -12430,8 +12488,8 @@ async function handleRecord(args, options, mode) {
|
|
|
12430
12488
|
}, mode);
|
|
12431
12489
|
const md = SessionRecorder.readMarkdownSummary(sessionName);
|
|
12432
12490
|
if (md) {
|
|
12433
|
-
|
|
12434
|
-
|
|
12491
|
+
process.stdout.write("\n");
|
|
12492
|
+
process.stdout.write(md + "\n");
|
|
12435
12493
|
} else {
|
|
12436
12494
|
const summary = SessionRecorder.readSummary(sessionName);
|
|
12437
12495
|
if (summary) {
|
|
@@ -12490,25 +12548,30 @@ async function handleRecord(args, options, mode) {
|
|
|
12490
12548
|
break;
|
|
12491
12549
|
}
|
|
12492
12550
|
default:
|
|
12493
|
-
|
|
12494
|
-
|
|
12495
|
-
|
|
12496
|
-
|
|
12497
|
-
|
|
12498
|
-
|
|
12499
|
-
|
|
12500
|
-
|
|
12501
|
-
|
|
12551
|
+
process.stdout.write("Usage:\n");
|
|
12552
|
+
process.stdout.write(" xbrowser record start [--url <url>] [--session <name>]\n");
|
|
12553
|
+
process.stdout.write(" xbrowser record stop [--session <name>]\n");
|
|
12554
|
+
process.stdout.write(" xbrowser record status [--session <name>]\n");
|
|
12555
|
+
process.stdout.write(" xbrowser record summary [--session <name>] [--json]\n");
|
|
12556
|
+
process.stdout.write(' xbrowser record checkpoint --type <type> --hint "description" [--selector <sel>] [--session <name>]\n');
|
|
12557
|
+
process.stdout.write(" xbrowser record generate-plugin [--session <name>] [--name <plugin>] [--output <dir>]\n");
|
|
12558
|
+
process.stdout.write("\n");
|
|
12559
|
+
process.stdout.write("Checkpoint types: dialog, captcha, login, iframe, slider, custom\n");
|
|
12502
12560
|
}
|
|
12503
12561
|
}
|
|
12504
12562
|
function printRecordingSummary(summary, sessionName) {
|
|
12505
|
-
|
|
12506
|
-
|
|
12507
|
-
|
|
12508
|
-
|
|
12509
|
-
|
|
12510
|
-
|
|
12511
|
-
|
|
12563
|
+
process.stdout.write("\n");
|
|
12564
|
+
process.stdout.write("=== Recording Summary ===\n");
|
|
12565
|
+
process.stdout.write(` Start URL: ${summary.startUrl}
|
|
12566
|
+
`);
|
|
12567
|
+
process.stdout.write(` Duration: ${Math.round(summary.durationMs / 1e3)}s
|
|
12568
|
+
`);
|
|
12569
|
+
process.stdout.write(` Actions: ${summary.totalActions}
|
|
12570
|
+
`);
|
|
12571
|
+
process.stdout.write(` Network: ${summary.totalNetworkRequests}
|
|
12572
|
+
`);
|
|
12573
|
+
process.stdout.write(` Steps: ${summary.steps.length}
|
|
12574
|
+
`);
|
|
12512
12575
|
for (const step of summary.steps) {
|
|
12513
12576
|
const a = step.action;
|
|
12514
12577
|
const el = a.element;
|
|
@@ -12530,20 +12593,24 @@ function printRecordingSummary(summary, sessionName) {
|
|
|
12530
12593
|
}
|
|
12531
12594
|
const navInfo = step.contextChanges.find((c) => c.type === "navigate");
|
|
12532
12595
|
if (navInfo) desc += ` \u2192 navigate to ${navInfo.url?.substring(0, 80)}`;
|
|
12533
|
-
|
|
12596
|
+
process.stdout.write(` ${step.step}. ${desc}
|
|
12597
|
+
`);
|
|
12534
12598
|
if (a.clickContext) {
|
|
12535
12599
|
const ctx = a.clickContext;
|
|
12536
12600
|
if (ctx.appeared?.length > 0) {
|
|
12537
12601
|
for (const popup of ctx.appeared) {
|
|
12538
12602
|
const roleStr = popup.role ? ` [${popup.role}]` : "";
|
|
12539
|
-
|
|
12603
|
+
process.stdout.write(` \u21B3 ${popup.tag}${roleStr} "${(popup.text || "").substring(0, 60)}"
|
|
12604
|
+
`);
|
|
12540
12605
|
if (popup.items?.length > 0) {
|
|
12541
12606
|
for (const item of popup.items.slice(0, 10)) {
|
|
12542
12607
|
const disStr = item.disabled ? " [disabled]" : "";
|
|
12543
|
-
|
|
12608
|
+
process.stdout.write(` \u2022 ${item.text}${disStr}
|
|
12609
|
+
`);
|
|
12544
12610
|
}
|
|
12545
12611
|
if (popup.items.length > 10) {
|
|
12546
|
-
|
|
12612
|
+
process.stdout.write(` ... and ${popup.items.length - 10} more items
|
|
12613
|
+
`);
|
|
12547
12614
|
}
|
|
12548
12615
|
}
|
|
12549
12616
|
}
|
|
@@ -12556,31 +12623,41 @@ function printRecordingSummary(summary, sessionName) {
|
|
|
12556
12623
|
if (sc.ariaSelected !== void 0) parts.push(`selected=${sc.ariaSelected}`);
|
|
12557
12624
|
if (sc.dataState) parts.push(`state=${sc.dataState}`);
|
|
12558
12625
|
if (parts.length > 0) {
|
|
12559
|
-
|
|
12626
|
+
process.stdout.write(` \u21B3 state: <${sc.tag}> "${(sc.text || "").substring(0, 30)}" ${parts.join(", ")}
|
|
12627
|
+
`);
|
|
12560
12628
|
}
|
|
12561
12629
|
}
|
|
12562
12630
|
}
|
|
12563
12631
|
}
|
|
12564
12632
|
}
|
|
12565
|
-
|
|
12566
|
-
|
|
12633
|
+
process.stdout.write("\n");
|
|
12634
|
+
process.stdout.write(` Files: ${SessionRecorder.getRecordingsDir(sessionName)}/
|
|
12635
|
+
`);
|
|
12567
12636
|
if (summary.checkpoints && summary.checkpoints.length > 0) {
|
|
12568
|
-
|
|
12569
|
-
|
|
12637
|
+
process.stdout.write("\n");
|
|
12638
|
+
process.stdout.write(` Checkpoints (${summary.checkpoints.length}):
|
|
12639
|
+
`);
|
|
12570
12640
|
for (const cp of summary.checkpoints) {
|
|
12571
12641
|
const src = cp.source === "auto" ? "[auto]" : "[manual]";
|
|
12572
|
-
|
|
12573
|
-
|
|
12642
|
+
process.stdout.write(` ${cp.id}. ${src} [${cp.type}] ${cp.hint}
|
|
12643
|
+
`);
|
|
12644
|
+
if (cp.selector) process.stdout.write(` selector: ${cp.selector}
|
|
12645
|
+
`);
|
|
12574
12646
|
}
|
|
12575
12647
|
}
|
|
12576
12648
|
}
|
|
12577
12649
|
function printHumanReadableSummary(summary) {
|
|
12578
|
-
|
|
12579
|
-
|
|
12580
|
-
|
|
12581
|
-
|
|
12582
|
-
|
|
12583
|
-
|
|
12650
|
+
process.stdout.write(`Start URL: ${summary.startUrl}
|
|
12651
|
+
`);
|
|
12652
|
+
process.stdout.write(`Recorded: ${summary.recordedAt}
|
|
12653
|
+
`);
|
|
12654
|
+
process.stdout.write(`Duration: ${Math.round(summary.durationMs / 1e3)}s
|
|
12655
|
+
`);
|
|
12656
|
+
process.stdout.write(`Actions: ${summary.totalActions}
|
|
12657
|
+
`);
|
|
12658
|
+
process.stdout.write(`Network: ${summary.totalNetworkRequests}
|
|
12659
|
+
`);
|
|
12660
|
+
process.stdout.write("\n");
|
|
12584
12661
|
for (const step of summary.steps) {
|
|
12585
12662
|
const a = step.action;
|
|
12586
12663
|
const el = a.element;
|
|
@@ -12596,20 +12673,23 @@ function printHumanReadableSummary(summary) {
|
|
|
12596
12673
|
if (a.value) parts.push(`value="${a.value.substring(0, 50)}"`);
|
|
12597
12674
|
if (a.key) parts.push(`key=${a.key}`);
|
|
12598
12675
|
if (a.x !== void 0 && a.y !== void 0) parts.push(`@(${a.x},${a.y})`);
|
|
12599
|
-
|
|
12676
|
+
process.stdout.write(parts.join(" ") + "\n");
|
|
12600
12677
|
if (a.clickContext) {
|
|
12601
12678
|
const ctx = a.clickContext;
|
|
12602
12679
|
if (ctx.appeared?.length > 0) {
|
|
12603
12680
|
for (const popup of ctx.appeared) {
|
|
12604
12681
|
const roleStr = popup.role ? ` [${popup.role}]` : "";
|
|
12605
|
-
|
|
12682
|
+
process.stdout.write(` \u{1F4CB} ${popup.tag}${roleStr} "${(popup.text || "").substring(0, 60)}"
|
|
12683
|
+
`);
|
|
12606
12684
|
if (popup.items?.length > 0) {
|
|
12607
12685
|
for (const item of popup.items.slice(0, 10)) {
|
|
12608
12686
|
const disStr = item.disabled ? " [disabled]" : "";
|
|
12609
|
-
|
|
12687
|
+
process.stdout.write(` \u2022 ${item.text}${disStr}
|
|
12688
|
+
`);
|
|
12610
12689
|
}
|
|
12611
12690
|
if (popup.items.length > 10) {
|
|
12612
|
-
|
|
12691
|
+
process.stdout.write(` ... and ${popup.items.length - 10} more items
|
|
12692
|
+
`);
|
|
12613
12693
|
}
|
|
12614
12694
|
}
|
|
12615
12695
|
}
|
|
@@ -12622,40 +12702,50 @@ function printHumanReadableSummary(summary) {
|
|
|
12622
12702
|
if (sc.ariaSelected !== void 0) stateParts.push(`selected=${sc.ariaSelected}`);
|
|
12623
12703
|
if (sc.dataState) stateParts.push(`state=${sc.dataState}`);
|
|
12624
12704
|
if (stateParts.length > 0) {
|
|
12625
|
-
|
|
12705
|
+
process.stdout.write(` \u{1F504} <${sc.tag}> "${(sc.text || "").substring(0, 30)}" ${stateParts.join(", ")}
|
|
12706
|
+
`);
|
|
12626
12707
|
}
|
|
12627
12708
|
}
|
|
12628
12709
|
}
|
|
12629
12710
|
}
|
|
12630
12711
|
for (const net of step.network) {
|
|
12631
|
-
|
|
12712
|
+
process.stdout.write(` \u2192 ${net.method} ${net.path} [${net.status}] ${net.resourceType}
|
|
12713
|
+
`);
|
|
12632
12714
|
if (net.requestBody && typeof net.requestBody === "object") {
|
|
12633
12715
|
const bodyStr = JSON.stringify(net.requestBody);
|
|
12634
12716
|
if (bodyStr.length <= 200) {
|
|
12635
|
-
|
|
12717
|
+
process.stdout.write(` body: ${bodyStr}
|
|
12718
|
+
`);
|
|
12636
12719
|
} else {
|
|
12637
|
-
|
|
12720
|
+
process.stdout.write(` body: ${bodyStr.substring(0, 200)}... (${bodyStr.length} bytes)
|
|
12721
|
+
`);
|
|
12638
12722
|
}
|
|
12639
12723
|
}
|
|
12640
12724
|
}
|
|
12641
12725
|
for (const match of step.matchedInputs) {
|
|
12642
|
-
|
|
12726
|
+
process.stdout.write(` \u{1F517} input "${match.inputValue}" \u2192 network #${match.networkId} param "${match.paramName}"
|
|
12727
|
+
`);
|
|
12643
12728
|
}
|
|
12644
12729
|
for (const ctx of step.contextChanges) {
|
|
12645
12730
|
if (ctx.type === "navigate") {
|
|
12646
|
-
|
|
12731
|
+
process.stdout.write(` \u2197 navigate \u2192 ${ctx.url}
|
|
12732
|
+
`);
|
|
12647
12733
|
} else if (ctx.type === "new_tab") {
|
|
12648
|
-
|
|
12734
|
+
process.stdout.write(` \u2197 new tab: ${ctx.url}
|
|
12735
|
+
`);
|
|
12649
12736
|
}
|
|
12650
12737
|
}
|
|
12651
12738
|
}
|
|
12652
12739
|
if (summary.checkpoints && summary.checkpoints.length > 0) {
|
|
12653
|
-
|
|
12654
|
-
|
|
12740
|
+
process.stdout.write("\n");
|
|
12741
|
+
process.stdout.write(`Checkpoints (${summary.checkpoints.length}):
|
|
12742
|
+
`);
|
|
12655
12743
|
for (const cp of summary.checkpoints) {
|
|
12656
12744
|
const src = cp.source === "auto" ? "[auto]" : "[manual]";
|
|
12657
|
-
|
|
12658
|
-
|
|
12745
|
+
process.stdout.write(` ${cp.id}. ${src} [${cp.type}] ${cp.hint}
|
|
12746
|
+
`);
|
|
12747
|
+
if (cp.selector) process.stdout.write(` selector: ${cp.selector}
|
|
12748
|
+
`);
|
|
12659
12749
|
}
|
|
12660
12750
|
}
|
|
12661
12751
|
}
|
|
@@ -12681,13 +12771,25 @@ async function handleReplay(args, options, mode) {
|
|
|
12681
12771
|
}
|
|
12682
12772
|
return;
|
|
12683
12773
|
}
|
|
12774
|
+
if (result.ok && mode !== "json" && mode !== "yaml") {
|
|
12775
|
+
const healed = typeof result.healed === "number" ? result.healed : 0;
|
|
12776
|
+
if (healed > 0) {
|
|
12777
|
+
process.stdout.write(`
|
|
12778
|
+
Self-healed ${healed} action(s):
|
|
12779
|
+
`);
|
|
12780
|
+
for (const d of result.healedDetails ?? []) {
|
|
12781
|
+
process.stdout.write(` - step ${d.index + 1}: ${d.strategy}
|
|
12782
|
+
`);
|
|
12783
|
+
}
|
|
12784
|
+
}
|
|
12785
|
+
}
|
|
12684
12786
|
outputResult(result, mode);
|
|
12685
12787
|
}
|
|
12686
12788
|
async function handleConvert(args, _mode) {
|
|
12687
12789
|
const filePath = args[0];
|
|
12688
12790
|
const outputPath = args[1];
|
|
12689
12791
|
if (!filePath || !outputPath) {
|
|
12690
|
-
|
|
12792
|
+
outputError("Usage: xbrowser convert <recording.yaml> <output.{js,py,sh}>");
|
|
12691
12793
|
process.exit(1);
|
|
12692
12794
|
}
|
|
12693
12795
|
const fs6 = await import("fs");
|
|
@@ -12699,11 +12801,11 @@ async function handleConvert(args, _mode) {
|
|
|
12699
12801
|
const content = fs6.readFileSync(filePath, "utf-8");
|
|
12700
12802
|
recording = yaml3.parse(content);
|
|
12701
12803
|
} catch (e) {
|
|
12702
|
-
|
|
12804
|
+
outputError(`Error: Failed to read "${filePath}": ${e instanceof Error ? e.message.split("\n")[0] : String(e)}`);
|
|
12703
12805
|
process.exit(1);
|
|
12704
12806
|
}
|
|
12705
12807
|
if (recording === null || typeof recording !== "object" || Array.isArray(recording)) {
|
|
12706
|
-
|
|
12808
|
+
outputError(`Error: "${filePath}" does not contain a valid recording (expected a YAML/JSON object with events or actions).`);
|
|
12707
12809
|
process.exit(1);
|
|
12708
12810
|
}
|
|
12709
12811
|
const rawActions = recording.actions;
|
|
@@ -12737,24 +12839,28 @@ async function handleConvert(args, _mode) {
|
|
|
12737
12839
|
fs6.writeFileSync(outputPath, script);
|
|
12738
12840
|
fs6.chmodSync(outputPath, 493);
|
|
12739
12841
|
const eventCount = (recordingTyped.events || []).length;
|
|
12740
|
-
|
|
12741
|
-
|
|
12742
|
-
|
|
12842
|
+
process.stdout.write(`Converted ${filePath} -> ${outputPath}
|
|
12843
|
+
`);
|
|
12844
|
+
process.stdout.write(` Events: ${eventCount}, Start URL: ${recordingTyped.startUrl}
|
|
12845
|
+
`);
|
|
12846
|
+
process.stdout.write(` Run: ${ext === ".py" ? "python" : ext === ".sh" ? "./" : "node"} ${outputPath}
|
|
12847
|
+
`);
|
|
12743
12848
|
}
|
|
12744
12849
|
async function handleExtract(args, _mode) {
|
|
12745
12850
|
const filePath = args[0];
|
|
12746
12851
|
if (!filePath) {
|
|
12747
|
-
|
|
12852
|
+
outputError("Usage: xbrowser extract <recording.yaml>");
|
|
12748
12853
|
process.exit(1);
|
|
12749
12854
|
}
|
|
12750
12855
|
const { extractAndSave: extractAndSave2, printExtractSummary: printExtractSummary2 } = await import("./extract-DSU2RVMN.js");
|
|
12751
12856
|
try {
|
|
12752
12857
|
const { summary, outputPath } = extractAndSave2(filePath);
|
|
12753
12858
|
printExtractSummary2(summary);
|
|
12754
|
-
|
|
12755
|
-
Saved LLM summary: ${outputPath}
|
|
12859
|
+
process.stdout.write(`
|
|
12860
|
+
Saved LLM summary: ${outputPath}
|
|
12861
|
+
`);
|
|
12756
12862
|
} catch (e) {
|
|
12757
|
-
|
|
12863
|
+
outputError(`Error: ${e instanceof Error ? e.message : String(e)}`);
|
|
12758
12864
|
process.exit(1);
|
|
12759
12865
|
}
|
|
12760
12866
|
}
|
|
@@ -12762,7 +12868,7 @@ async function handleFilter(args, _mode, options) {
|
|
|
12762
12868
|
const filePath = args[0];
|
|
12763
12869
|
const outputPath = args[1];
|
|
12764
12870
|
if (!filePath || !outputPath) {
|
|
12765
|
-
|
|
12871
|
+
outputError("Usage: xbrowser filter <input.yaml> <output.yaml> [--exclude type1,type2]");
|
|
12766
12872
|
process.exit(1);
|
|
12767
12873
|
}
|
|
12768
12874
|
const { filterRecording: filterRecording2, parseExcludeTypes: parseExcludeTypes2 } = await import("./filter-3DXC6432.js");
|
|
@@ -12774,15 +12880,17 @@ async function handleFilter(args, _mode, options) {
|
|
|
12774
12880
|
const excludeTypes = parseExcludeTypes2(excludeArgs);
|
|
12775
12881
|
try {
|
|
12776
12882
|
const result = filterRecording2(filePath, outputPath, excludeTypes);
|
|
12777
|
-
|
|
12778
|
-
|
|
12883
|
+
process.stdout.write(`Filtered ${filePath} -> ${outputPath}
|
|
12884
|
+
`);
|
|
12885
|
+
process.stdout.write(` Original: ${result.originalCount}, After: ${result.filteredCount}, Removed: ${result.removed} (${result.percentage}%)
|
|
12886
|
+
`);
|
|
12779
12887
|
} catch (e) {
|
|
12780
|
-
|
|
12888
|
+
outputError(`Error: ${e instanceof Error ? e.message : String(e)}`);
|
|
12781
12889
|
process.exit(1);
|
|
12782
12890
|
}
|
|
12783
12891
|
}
|
|
12784
12892
|
async function handleGeneratePlugin(sessionName, pluginName, outputDir) {
|
|
12785
|
-
const { SessionRecorder: SessionRecorder2 } = await import("./session-recorder-
|
|
12893
|
+
const { SessionRecorder: SessionRecorder2 } = await import("./session-recorder-563ISSJK.js");
|
|
12786
12894
|
const { readSiteKnowledge: readSiteKnowledge2, toMarkdown } = await import("./site-knowledge-SYC6VCDB.js");
|
|
12787
12895
|
const { mkdirSync: mkdirSync11, writeFileSync: writeFileSync13 } = await import("fs");
|
|
12788
12896
|
const { join: join16 } = await import("path");
|
|
@@ -12806,21 +12914,30 @@ async function handleGeneratePlugin(sessionName, pluginName, outputDir) {
|
|
|
12806
12914
|
if (knowledgeMd) {
|
|
12807
12915
|
writeFileSync13(join16(finalOutputDir, "SITE_KNOWLEDGE.md"), knowledgeMd, "utf-8");
|
|
12808
12916
|
}
|
|
12809
|
-
|
|
12810
|
-
|
|
12811
|
-
|
|
12812
|
-
|
|
12813
|
-
|
|
12917
|
+
process.stdout.write("\n");
|
|
12918
|
+
process.stdout.write("=== Plugin Generated ===\n");
|
|
12919
|
+
process.stdout.write(` Plugin: ${finalPluginName}
|
|
12920
|
+
`);
|
|
12921
|
+
process.stdout.write(` Domain: ${domain}
|
|
12922
|
+
`);
|
|
12923
|
+
process.stdout.write(` Output: ${finalOutputDir}/index.ts
|
|
12924
|
+
`);
|
|
12814
12925
|
if (knowledgeMd) {
|
|
12815
|
-
|
|
12926
|
+
process.stdout.write(` Knowledge: ${finalOutputDir}/SITE_KNOWLEDGE.md
|
|
12927
|
+
`);
|
|
12816
12928
|
}
|
|
12817
|
-
|
|
12818
|
-
|
|
12819
|
-
|
|
12820
|
-
|
|
12821
|
-
|
|
12822
|
-
|
|
12823
|
-
|
|
12929
|
+
process.stdout.write(` Actions: ${data.actions.length}
|
|
12930
|
+
`);
|
|
12931
|
+
process.stdout.write(` APIs: ${data.network.filter((n) => n.contentType.includes("json") || n.url.includes("/api/")).length}
|
|
12932
|
+
`);
|
|
12933
|
+
process.stdout.write("\n");
|
|
12934
|
+
process.stdout.write("Next steps:\n");
|
|
12935
|
+
process.stdout.write(` 1. Review and edit: ${finalOutputDir}/index.ts
|
|
12936
|
+
`);
|
|
12937
|
+
process.stdout.write(` 2. Test: xbrowser ${finalPluginName} <command>
|
|
12938
|
+
`);
|
|
12939
|
+
process.stdout.write(` 3. Reference: ${finalOutputDir}/SITE_KNOWLEDGE.md (for LLM)
|
|
12940
|
+
`);
|
|
12824
12941
|
}
|
|
12825
12942
|
function generatePluginCode(pluginName, domain, data, _knowledgeMd) {
|
|
12826
12943
|
const pagePaths = /* @__PURE__ */ new Set();
|
|
@@ -12999,20 +13116,22 @@ async function handleRun(filePath, options) {
|
|
|
12999
13116
|
});
|
|
13000
13117
|
for (const step of chainResult.steps) {
|
|
13001
13118
|
if (step.success) {
|
|
13002
|
-
|
|
13119
|
+
process.stdout.write(`[OK] ${step.raw}
|
|
13120
|
+
`);
|
|
13003
13121
|
if (step.data && typeof step.data === "object") {
|
|
13004
13122
|
const d = step.data;
|
|
13005
13123
|
for (const [k, v] of Object.entries(d)) {
|
|
13006
13124
|
if (k !== "ok")
|
|
13007
|
-
|
|
13125
|
+
process.stdout.write(` ${k}: ${typeof v === "string" ? v : JSON.stringify(v)}
|
|
13126
|
+
`);
|
|
13008
13127
|
}
|
|
13009
13128
|
}
|
|
13010
13129
|
} else {
|
|
13011
|
-
|
|
13130
|
+
outputError(`[FAIL] ${step.raw}: ${step.message}`);
|
|
13012
13131
|
}
|
|
13013
13132
|
}
|
|
13014
13133
|
if (chainResult.stoppedReason) {
|
|
13015
|
-
|
|
13134
|
+
outputError(`Stopped: ${chainResult.stoppedReason}`);
|
|
13016
13135
|
}
|
|
13017
13136
|
if (!chainResult.success) process.exit(1);
|
|
13018
13137
|
}
|
|
@@ -13055,26 +13174,29 @@ async function handleNetCommand(args, options, mode, sessionName) {
|
|
|
13055
13174
|
if (mode === "json") {
|
|
13056
13175
|
outputResult(result, mode);
|
|
13057
13176
|
} else {
|
|
13058
|
-
|
|
13177
|
+
process.stdout.write(`
|
|
13059
13178
|
Network captures (session: ${netSession})`);
|
|
13060
|
-
|
|
13179
|
+
process.stdout.write(` Total: ${result.total}, Showing: ${result.captures.length}
|
|
13061
13180
|
`);
|
|
13062
13181
|
for (const c of result.captures) {
|
|
13063
13182
|
const statusColor = c.status < 300 ? "\x1B[32m" : c.status < 400 ? "\x1B[33m" : "\x1B[31m";
|
|
13064
13183
|
const reset = "\x1B[0m";
|
|
13065
|
-
|
|
13184
|
+
process.stdout.write(` #${c.id} ${c.method.padEnd(6)} ${statusColor}${c.status}${reset} ${c.resourceType.padEnd(10)} ${c.path}
|
|
13185
|
+
`);
|
|
13066
13186
|
if (c.size > 0) {
|
|
13067
13187
|
const sizeStr = c.size > 1024 ? `${(c.size / 1024).toFixed(1)}KB` : `${c.size}B`;
|
|
13068
|
-
|
|
13188
|
+
process.stdout.write(` ${c.contentType.split(";")[0]} ${sizeStr}
|
|
13189
|
+
`);
|
|
13069
13190
|
}
|
|
13070
13191
|
}
|
|
13071
|
-
|
|
13192
|
+
process.stdout.write("\n");
|
|
13072
13193
|
}
|
|
13073
13194
|
break;
|
|
13074
13195
|
}
|
|
13075
13196
|
case "clear": {
|
|
13076
13197
|
await forwardNetworkClear(netSession);
|
|
13077
|
-
|
|
13198
|
+
process.stdout.write(`Network captures cleared for session: ${netSession}
|
|
13199
|
+
`);
|
|
13078
13200
|
break;
|
|
13079
13201
|
}
|
|
13080
13202
|
case "top": {
|
|
@@ -13084,21 +13206,23 @@ async function handleNetCommand(args, options, mode, sessionName) {
|
|
|
13084
13206
|
if (mode === "json") {
|
|
13085
13207
|
outputResult(result, mode);
|
|
13086
13208
|
} else {
|
|
13087
|
-
|
|
13209
|
+
process.stdout.write(`
|
|
13088
13210
|
Top valued requests (session: ${netSession})`);
|
|
13089
|
-
|
|
13211
|
+
process.stdout.write(` Showing: ${result.entries.length}
|
|
13090
13212
|
`);
|
|
13091
13213
|
for (const e of result.entries) {
|
|
13092
13214
|
const scoreColor = e.score >= 50 ? "\x1B[32m" : e.score >= 20 ? "\x1B[33m" : "\x1B[90m";
|
|
13093
13215
|
const reset = "\x1B[0m";
|
|
13094
13216
|
const methodStr = e.method.padEnd(6);
|
|
13095
13217
|
const scoreStr = `${scoreColor}${e.score.toString().padStart(3)}${reset}`;
|
|
13096
|
-
|
|
13218
|
+
process.stdout.write(` ${scoreStr} ${methodStr} ${e.status} ${e.resourceType.padEnd(10)} ${e.path}
|
|
13219
|
+
`);
|
|
13097
13220
|
if (e.scoreBreakdown.content > 0) {
|
|
13098
|
-
|
|
13221
|
+
process.stdout.write(` ${e.contentType.split(";")[0]} ${e.size > 1024 ? (e.size / 1024).toFixed(1) + "KB" : e.size + "B"}
|
|
13222
|
+
`);
|
|
13099
13223
|
}
|
|
13100
13224
|
}
|
|
13101
|
-
|
|
13225
|
+
process.stdout.write("\n");
|
|
13102
13226
|
}
|
|
13103
13227
|
break;
|
|
13104
13228
|
}
|
|
@@ -13107,16 +13231,17 @@ async function handleNetCommand(args, options, mode, sessionName) {
|
|
|
13107
13231
|
if (mode === "json") {
|
|
13108
13232
|
outputResult(logResult, mode);
|
|
13109
13233
|
} else {
|
|
13110
|
-
|
|
13234
|
+
process.stdout.write(`
|
|
13111
13235
|
Command log (session: ${netSession})`);
|
|
13112
|
-
|
|
13236
|
+
process.stdout.write(` Total: ${logResult.commands.length}
|
|
13113
13237
|
`);
|
|
13114
13238
|
for (const cmd of logResult.commands) {
|
|
13115
13239
|
const ts = new Date(cmd.timestamp).toISOString().substring(11, 19);
|
|
13116
13240
|
const paramsStr = Object.entries(cmd.params).map(([k, v]) => `${k}=${v}`).join(" ");
|
|
13117
|
-
|
|
13241
|
+
process.stdout.write(` #${cmd.id} [${ts}] ${cmd.command} ${paramsStr}
|
|
13242
|
+
`);
|
|
13118
13243
|
}
|
|
13119
|
-
|
|
13244
|
+
process.stdout.write("\n");
|
|
13120
13245
|
}
|
|
13121
13246
|
break;
|
|
13122
13247
|
}
|
|
@@ -13132,28 +13257,31 @@ async function handleNetCommand(args, options, mode, sessionName) {
|
|
|
13132
13257
|
outputResult(aroundResult, mode);
|
|
13133
13258
|
} else {
|
|
13134
13259
|
if (!aroundResult) {
|
|
13135
|
-
|
|
13260
|
+
process.stdout.write(" No command found with that ID\n");
|
|
13136
13261
|
break;
|
|
13137
13262
|
}
|
|
13138
13263
|
const cmd = aroundResult.command;
|
|
13139
13264
|
const ts = new Date(cmd.timestamp).toISOString().substring(11, 19);
|
|
13140
|
-
|
|
13265
|
+
process.stdout.write(`
|
|
13141
13266
|
Command: #${cmd.id} [${ts}] ${cmd.command}`);
|
|
13142
|
-
|
|
13267
|
+
process.stdout.write(` Window: \xB1${windowMs}ms
|
|
13143
13268
|
`);
|
|
13144
13269
|
const before = aroundResult.before;
|
|
13145
13270
|
const after = aroundResult.after;
|
|
13146
|
-
|
|
13271
|
+
process.stdout.write(` BEFORE (${before.length} requests):
|
|
13272
|
+
`);
|
|
13147
13273
|
for (const r of before.slice(0, 5)) {
|
|
13148
|
-
|
|
13274
|
+
process.stdout.write(` ${r.method} ${r.status} ${String(r.resourceType).padEnd(10)} ${r.path}
|
|
13275
|
+
`);
|
|
13149
13276
|
}
|
|
13150
|
-
|
|
13277
|
+
process.stdout.write(`
|
|
13151
13278
|
AFTER (${aroundResult.afterCount} requests):`);
|
|
13152
13279
|
for (const r of after.slice(0, 10)) {
|
|
13153
13280
|
const highlight = r.method !== "GET" ? " \u2190" : "";
|
|
13154
|
-
|
|
13281
|
+
process.stdout.write(` ${String(r.method).padEnd(6)} ${r.status} ${String(r.resourceType).padEnd(10)} ${r.path}${highlight}
|
|
13282
|
+
`);
|
|
13155
13283
|
}
|
|
13156
|
-
|
|
13284
|
+
process.stdout.write("\n");
|
|
13157
13285
|
}
|
|
13158
13286
|
break;
|
|
13159
13287
|
}
|
|
@@ -13162,9 +13290,9 @@ async function handleNetCommand(args, options, mode, sessionName) {
|
|
|
13162
13290
|
if (mode === "json") {
|
|
13163
13291
|
outputResult(result, mode);
|
|
13164
13292
|
} else {
|
|
13165
|
-
|
|
13293
|
+
process.stdout.write(`
|
|
13166
13294
|
API Reusability Analysis (session: ${netSession})`);
|
|
13167
|
-
|
|
13295
|
+
process.stdout.write(` Total: ${result.total}, Analyzed: ${result.analyzed.length}
|
|
13168
13296
|
`);
|
|
13169
13297
|
const groups = { high: [], medium: [], low: [], unknown: [] };
|
|
13170
13298
|
for (const e of result.analyzed) {
|
|
@@ -13175,16 +13303,20 @@ async function handleNetCommand(args, options, mode, sessionName) {
|
|
|
13175
13303
|
if (!items?.length) continue;
|
|
13176
13304
|
const color = level === "high" ? "\x1B[32m" : level === "medium" ? "\x1B[33m" : level === "low" ? "\x1B[31m" : "\x1B[90m";
|
|
13177
13305
|
const reset = "\x1B[0m";
|
|
13178
|
-
|
|
13306
|
+
process.stdout.write(` ${color}${level.toUpperCase()}${reset} (${items.length})
|
|
13307
|
+
`);
|
|
13179
13308
|
for (const e of items.slice(0, 5)) {
|
|
13180
13309
|
const scoreStr = `[${e.reusability.score.toString().padStart(3)}]`;
|
|
13181
|
-
|
|
13310
|
+
process.stdout.write(` ${e.method.padEnd(6)} ${e.status} ${scoreStr} ${e.path}
|
|
13311
|
+
`);
|
|
13182
13312
|
if (e.reusability.reasons.length > 0) {
|
|
13183
|
-
|
|
13313
|
+
process.stdout.write(` ${e.reusability.reasons.join(", ")}
|
|
13314
|
+
`);
|
|
13184
13315
|
}
|
|
13185
13316
|
}
|
|
13186
|
-
if (items.length > 5)
|
|
13187
|
-
|
|
13317
|
+
if (items.length > 5) process.stdout.write(` ... and ${items.length - 5} more
|
|
13318
|
+
`);
|
|
13319
|
+
process.stdout.write("\n");
|
|
13188
13320
|
}
|
|
13189
13321
|
}
|
|
13190
13322
|
break;
|
|
@@ -13203,12 +13335,12 @@ async function handleNetCommand(args, options, mode, sessionName) {
|
|
|
13203
13335
|
if (mode === "json") {
|
|
13204
13336
|
outputResult(result, mode);
|
|
13205
13337
|
} else {
|
|
13206
|
-
|
|
13338
|
+
process.stdout.write(`
|
|
13207
13339
|
${result.method} ${result.url}`);
|
|
13208
|
-
|
|
13340
|
+
process.stdout.write(` Headers: ${result.headerCount}, Body: ${result.hasBody}
|
|
13209
13341
|
`);
|
|
13210
|
-
|
|
13211
|
-
|
|
13342
|
+
process.stdout.write(result.command + "\n");
|
|
13343
|
+
process.stdout.write("\n");
|
|
13212
13344
|
}
|
|
13213
13345
|
break;
|
|
13214
13346
|
}
|
|
@@ -13226,27 +13358,33 @@ async function handleNetCommand(args, options, mode, sessionName) {
|
|
|
13226
13358
|
if (mode === "json") {
|
|
13227
13359
|
outputResult(result, mode);
|
|
13228
13360
|
} else {
|
|
13229
|
-
|
|
13361
|
+
process.stdout.write(`
|
|
13230
13362
|
Replay Result`);
|
|
13231
|
-
|
|
13363
|
+
process.stdout.write(` ${result.curlCommand?.split("\n")[0]?.trim()}
|
|
13232
13364
|
`);
|
|
13233
13365
|
const replay = result.replay;
|
|
13234
13366
|
if (replay?.error) {
|
|
13235
|
-
|
|
13367
|
+
process.stdout.write(` \x1B[31mFAILED\x1B[0m: ${replay.error}
|
|
13368
|
+
`);
|
|
13236
13369
|
} else if (replay) {
|
|
13237
13370
|
const statusColor = replay.status && replay.status < 300 ? "\x1B[32m" : "\x1B[31m";
|
|
13238
13371
|
const status = replay.status;
|
|
13239
13372
|
const size = replay.size;
|
|
13240
13373
|
const duration = replay.duration;
|
|
13241
|
-
|
|
13242
|
-
|
|
13243
|
-
|
|
13244
|
-
|
|
13374
|
+
process.stdout.write(` Status: ${statusColor}${status}\x1B[0m ${replay.statusText}
|
|
13375
|
+
`);
|
|
13376
|
+
process.stdout.write(` Size: ${size > 1024 ? (size / 1024).toFixed(1) + "KB" : size + "B"}
|
|
13377
|
+
`);
|
|
13378
|
+
process.stdout.write(` Duration: ${duration}ms
|
|
13379
|
+
`);
|
|
13380
|
+
process.stdout.write(` Body Match: ${replay.bodyMatch ? "\x1B[32mYes\x1B[0m" : "\x1B[33mNo\x1B[0m"}
|
|
13381
|
+
`);
|
|
13245
13382
|
if (status && status >= 400) {
|
|
13246
|
-
|
|
13383
|
+
process.stdout.write(` \x1B[33m\u26A0 API may require fresh signature/token\x1B[0m
|
|
13384
|
+
`);
|
|
13247
13385
|
}
|
|
13248
13386
|
}
|
|
13249
|
-
|
|
13387
|
+
process.stdout.write("\n");
|
|
13250
13388
|
}
|
|
13251
13389
|
break;
|
|
13252
13390
|
}
|
|
@@ -13265,40 +13403,47 @@ async function handleNetCommand(args, options, mode, sessionName) {
|
|
|
13265
13403
|
outputResult(result, mode);
|
|
13266
13404
|
} else {
|
|
13267
13405
|
const c = result.capture;
|
|
13268
|
-
|
|
13406
|
+
process.stdout.write(`
|
|
13269
13407
|
Request #${c.id}`);
|
|
13270
|
-
|
|
13271
|
-
|
|
13272
|
-
|
|
13408
|
+
process.stdout.write(` ${c.method} ${c.url}
|
|
13409
|
+
`);
|
|
13410
|
+
process.stdout.write(` Status: ${c.status} | Size: ${c.size}B | Type: ${c.contentType}
|
|
13411
|
+
`);
|
|
13412
|
+
process.stdout.write(` Resource: ${c.resourceType}
|
|
13413
|
+
`);
|
|
13273
13414
|
if (c.requestHeaders) {
|
|
13274
|
-
|
|
13415
|
+
process.stdout.write(`
|
|
13275
13416
|
Request Headers:`);
|
|
13276
13417
|
for (const [k, v] of Object.entries(c.requestHeaders)) {
|
|
13277
|
-
|
|
13418
|
+
process.stdout.write(` ${k}: ${String(v).substring(0, 100)}
|
|
13419
|
+
`);
|
|
13278
13420
|
}
|
|
13279
13421
|
}
|
|
13280
13422
|
if (c.requestBody !== void 0) {
|
|
13281
|
-
|
|
13423
|
+
process.stdout.write(`
|
|
13282
13424
|
Request Body:`);
|
|
13283
13425
|
const bodyStr = typeof c.requestBody === "string" ? c.requestBody : JSON.stringify(c.requestBody, null, 2);
|
|
13284
13426
|
const lines = bodyStr.split("\n").slice(0, 20);
|
|
13285
|
-
for (const line of lines)
|
|
13286
|
-
|
|
13427
|
+
for (const line of lines) process.stdout.write(` ${line}
|
|
13428
|
+
`);
|
|
13429
|
+
if (bodyStr.split("\n").length > 20) process.stdout.write(" ...");
|
|
13287
13430
|
}
|
|
13288
|
-
|
|
13431
|
+
process.stdout.write(`
|
|
13289
13432
|
Response Headers:`);
|
|
13290
13433
|
for (const [k, v] of Object.entries(c.headers)) {
|
|
13291
|
-
|
|
13434
|
+
process.stdout.write(` ${k}: ${String(v).substring(0, 100)}
|
|
13435
|
+
`);
|
|
13292
13436
|
}
|
|
13293
13437
|
if (c.body !== void 0) {
|
|
13294
|
-
|
|
13438
|
+
process.stdout.write(`
|
|
13295
13439
|
Response Body:`);
|
|
13296
13440
|
const bodyStr = typeof c.body === "string" ? c.body : JSON.stringify(c.body, null, 2);
|
|
13297
13441
|
const lines = bodyStr.split("\n").slice(0, 20);
|
|
13298
|
-
for (const line of lines)
|
|
13299
|
-
|
|
13442
|
+
for (const line of lines) process.stdout.write(` ${line}
|
|
13443
|
+
`);
|
|
13444
|
+
if (bodyStr.split("\n").length > 20) process.stdout.write(" ...");
|
|
13300
13445
|
}
|
|
13301
|
-
|
|
13446
|
+
process.stdout.write("\n");
|
|
13302
13447
|
}
|
|
13303
13448
|
break;
|
|
13304
13449
|
}
|
|
@@ -13309,7 +13454,8 @@ async function handleNetCommand(args, options, mode, sessionName) {
|
|
|
13309
13454
|
break;
|
|
13310
13455
|
}
|
|
13311
13456
|
await forwardNetworkLike(netSession, id);
|
|
13312
|
-
|
|
13457
|
+
process.stdout.write(`Marked #${id} as useful
|
|
13458
|
+
`);
|
|
13313
13459
|
break;
|
|
13314
13460
|
}
|
|
13315
13461
|
case "dislike": {
|
|
@@ -13319,7 +13465,8 @@ async function handleNetCommand(args, options, mode, sessionName) {
|
|
|
13319
13465
|
break;
|
|
13320
13466
|
}
|
|
13321
13467
|
await forwardNetworkDislike(netSession, id);
|
|
13322
|
-
|
|
13468
|
+
process.stdout.write(`Marked #${id} as not useful
|
|
13469
|
+
`);
|
|
13323
13470
|
break;
|
|
13324
13471
|
}
|
|
13325
13472
|
case "export": {
|
|
@@ -13334,7 +13481,7 @@ async function handleNetCommand(args, options, mode, sessionName) {
|
|
|
13334
13481
|
outputError(result.error);
|
|
13335
13482
|
break;
|
|
13336
13483
|
}
|
|
13337
|
-
|
|
13484
|
+
process.stdout.write(result.code + "\n");
|
|
13338
13485
|
break;
|
|
13339
13486
|
}
|
|
13340
13487
|
default:
|
|
@@ -13346,9 +13493,25 @@ async function handleNetCommand(args, options, mode, sessionName) {
|
|
|
13346
13493
|
}
|
|
13347
13494
|
|
|
13348
13495
|
// src/cli/test-routes.ts
|
|
13349
|
-
import {
|
|
13496
|
+
import { execFile as execFile3 } from "child_process";
|
|
13350
13497
|
import { readFileSync as readFileSync11 } from "fs";
|
|
13351
13498
|
import { resolve as resolve9 } from "path";
|
|
13499
|
+
function execCli(args) {
|
|
13500
|
+
return new Promise((resolvePromise) => {
|
|
13501
|
+
execFile3(
|
|
13502
|
+
"npx",
|
|
13503
|
+
args,
|
|
13504
|
+
{ timeout: 65e3, env: { ...process.env, FORCE_COLOR: "0" } },
|
|
13505
|
+
(err, stdout, stderr) => {
|
|
13506
|
+
resolvePromise({
|
|
13507
|
+
stdout: typeof stdout === "string" ? stdout : stdout?.toString("utf-8") ?? "",
|
|
13508
|
+
stderr: typeof stderr === "string" ? stderr : stderr?.toString("utf-8") ?? "",
|
|
13509
|
+
error: err ?? null
|
|
13510
|
+
});
|
|
13511
|
+
}
|
|
13512
|
+
);
|
|
13513
|
+
});
|
|
13514
|
+
}
|
|
13352
13515
|
function findPluginPath(plugin) {
|
|
13353
13516
|
const candidates = [
|
|
13354
13517
|
resolve9(process.cwd(), ".xcli/plugins", plugin, "index.ts"),
|
|
@@ -13413,20 +13576,21 @@ function extractSchema(plugin, command) {
|
|
|
13413
13576
|
}
|
|
13414
13577
|
async function runTest(plugin, command, cmdArgs, options) {
|
|
13415
13578
|
const cdp = options.cdp || options.cdpEndpoint || "http://localhost:9221";
|
|
13416
|
-
const
|
|
13579
|
+
const passthroughArgs = cmdArgs.filter((a) => !a.startsWith("--cdp"));
|
|
13417
13580
|
const schema = extractSchema(plugin, command);
|
|
13418
|
-
const
|
|
13419
|
-
|
|
13420
|
-
|
|
13421
|
-
|
|
13422
|
-
|
|
13423
|
-
|
|
13424
|
-
|
|
13425
|
-
|
|
13426
|
-
|
|
13427
|
-
|
|
13428
|
-
|
|
13429
|
-
|
|
13581
|
+
const cliArgs = [
|
|
13582
|
+
"xbrowser",
|
|
13583
|
+
plugin,
|
|
13584
|
+
command,
|
|
13585
|
+
...passthroughArgs,
|
|
13586
|
+
"--cdp",
|
|
13587
|
+
String(cdp),
|
|
13588
|
+
"--json",
|
|
13589
|
+
"--timeout",
|
|
13590
|
+
"60000"
|
|
13591
|
+
];
|
|
13592
|
+
const { stdout, stderr, error } = await execCli(cliArgs);
|
|
13593
|
+
if (error) {
|
|
13430
13594
|
const jsonLine = stdout.split("\n").find((l) => {
|
|
13431
13595
|
try {
|
|
13432
13596
|
JSON.parse(l);
|
|
@@ -13445,11 +13609,10 @@ async function runTest(plugin, command, cmdArgs, options) {
|
|
|
13445
13609
|
} catch {
|
|
13446
13610
|
}
|
|
13447
13611
|
}
|
|
13448
|
-
const stderr = err.stderr?.toString() || "";
|
|
13449
13612
|
if (stdout.includes("captcha") || stderr.includes("captcha") || stdout.includes("CAPTCHA")) {
|
|
13450
13613
|
return { status: "CAPTCHA", message: "\u68C0\u6D4B\u5230\u9A8C\u8BC1\u7801", viewerUrl: "http://localhost:9224/preview/default" };
|
|
13451
13614
|
}
|
|
13452
|
-
return { status: "EXEC_ERROR", message: (
|
|
13615
|
+
return { status: "EXEC_ERROR", message: (error.message || "").slice(0, 200) || "\u6267\u884C\u5931\u8D25" };
|
|
13453
13616
|
}
|
|
13454
13617
|
const allLines = stdout.split("\n");
|
|
13455
13618
|
const jsonStart = allLines.findIndex((l) => l.trim().startsWith("{"));
|
|
@@ -13519,27 +13682,27 @@ async function handleTest(cmdArgs, options, mode, cdpEndpoint) {
|
|
|
13519
13682
|
const plugin = cmdArgs[0];
|
|
13520
13683
|
const command = cmdArgs[1];
|
|
13521
13684
|
if (!plugin || !command) {
|
|
13522
|
-
|
|
13523
|
-
|
|
13685
|
+
outputError("\u7528\u6CD5: xbrowser test <plugin> <command> [\u53C2\u6570...]");
|
|
13686
|
+
outputError("\u793A\u4F8B: xbrowser test doubao list --cdp 9221");
|
|
13524
13687
|
return;
|
|
13525
13688
|
}
|
|
13526
13689
|
const loader = await getPluginLoader();
|
|
13527
13690
|
const internalLoader = loader.getCore().loader;
|
|
13528
13691
|
const site = internalLoader.getSite(plugin);
|
|
13529
13692
|
if (!site) {
|
|
13530
|
-
|
|
13693
|
+
outputError(`\u63D2\u4EF6 "${plugin}" \u4E0D\u5B58\u5728`);
|
|
13531
13694
|
return;
|
|
13532
13695
|
}
|
|
13533
13696
|
const cmdEntry = site.getCommand(command);
|
|
13534
13697
|
if (!cmdEntry) {
|
|
13535
|
-
|
|
13698
|
+
outputError(`\u6307\u4EE4 "${command}" \u4E0D\u5B58\u5728`);
|
|
13536
13699
|
return;
|
|
13537
13700
|
}
|
|
13538
13701
|
const testArgs = cmdArgs.slice(2);
|
|
13539
13702
|
const mergedOptions = { ...options, cdp: cdpEndpoint || options.cdp };
|
|
13540
13703
|
const result = await runTest(plugin, command, testArgs, mergedOptions);
|
|
13541
13704
|
if (mode === "json") {
|
|
13542
|
-
|
|
13705
|
+
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
13543
13706
|
return;
|
|
13544
13707
|
}
|
|
13545
13708
|
const r = result;
|
|
@@ -13554,23 +13717,32 @@ async function handleTest(cmdArgs, options, mode, cdpEndpoint) {
|
|
|
13554
13717
|
};
|
|
13555
13718
|
const status = String(r.status);
|
|
13556
13719
|
const icon = icons[status] || "\u2753";
|
|
13557
|
-
|
|
13558
|
-
${icon} ${plugin}.${command}
|
|
13559
|
-
|
|
13720
|
+
process.stdout.write(`
|
|
13721
|
+
${icon} ${plugin}.${command}
|
|
13722
|
+
`);
|
|
13723
|
+
process.stdout.write(` \u72B6\u6001: ${status}
|
|
13724
|
+
`);
|
|
13560
13725
|
if (status === "OK") {
|
|
13561
|
-
if (r.count)
|
|
13562
|
-
|
|
13726
|
+
if (r.count) process.stdout.write(` \u6570\u636E: ${r.count} \u9879
|
|
13727
|
+
`);
|
|
13728
|
+
if (r.data) process.stdout.write(` \u9884\u89C8: ${String(r.data).slice(0, 150)}
|
|
13729
|
+
`);
|
|
13563
13730
|
} else if (status === "LOGIN_REQUIRED" || status === "CAPTCHA") {
|
|
13564
|
-
|
|
13565
|
-
|
|
13731
|
+
process.stdout.write(` \u4FE1\u606F: ${String(r.message)}
|
|
13732
|
+
`);
|
|
13733
|
+
process.stdout.write(` Viewer: ${String(r.viewerUrl)}`);
|
|
13566
13734
|
} else if (status === "SCHEMA_ERROR") {
|
|
13567
13735
|
const errs = r.errors;
|
|
13568
|
-
if (errs)
|
|
13736
|
+
if (errs) process.stdout.write(` \u9519\u8BEF: ${errs.join("; ")}
|
|
13737
|
+
`);
|
|
13569
13738
|
} else if (["NO_DATA", "BLOCKED"].includes(status)) {
|
|
13570
|
-
|
|
13571
|
-
|
|
13739
|
+
process.stdout.write(` \u4FE1\u606F: ${String(r.message)}
|
|
13740
|
+
`);
|
|
13741
|
+
if (r.viewerUrl) process.stdout.write(` Viewer: ${String(r.viewerUrl)}
|
|
13742
|
+
`);
|
|
13572
13743
|
} else {
|
|
13573
|
-
|
|
13744
|
+
process.stdout.write(` \u4FE1\u606F: ${String(r.message)}
|
|
13745
|
+
`);
|
|
13574
13746
|
}
|
|
13575
13747
|
}
|
|
13576
13748
|
|
|
@@ -13668,7 +13840,7 @@ Commands:
|
|
|
13668
13840
|
plugin list List plugins
|
|
13669
13841
|
plugin reload <name> Reload plugin
|
|
13670
13842
|
create <name> --template <type> Create plugin
|
|
13671
|
-
serve [--port <
|
|
13843
|
+
serve [--port <p>] [--host <h>] [--token <t>] [--cors-origins <csv>] Start HTTP server (loopback by default)
|
|
13672
13844
|
remote <url> [command] [--token <t>] Execute on remote server
|
|
13673
13845
|
run <file> Execute commands from file
|
|
13674
13846
|
viewer [--name <n>] Generate viewer URL
|
|
@@ -13789,10 +13961,26 @@ function validateAuth(authHeader, validTokens) {
|
|
|
13789
13961
|
function isAuthRequired(validTokens) {
|
|
13790
13962
|
return validTokens.length > 0;
|
|
13791
13963
|
}
|
|
13964
|
+
var IPV6_LOOPBACK = /* @__PURE__ */ new Set(["::1", "0:0:0:0:0:0:0:1"]);
|
|
13965
|
+
function isLoopbackHost(host) {
|
|
13966
|
+
const h = host.trim().toLowerCase().replace(/^\[/, "").replace(/\]$/, "");
|
|
13967
|
+
if (h === "localhost") return true;
|
|
13968
|
+
if (IPV6_LOOPBACK.has(h)) return true;
|
|
13969
|
+
const ipv4 = h.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/);
|
|
13970
|
+
return ipv4 !== null && ipv4[1] === "127";
|
|
13971
|
+
}
|
|
13972
|
+
function isLoopbackOrigin(origin) {
|
|
13973
|
+
try {
|
|
13974
|
+
const url = new URL(origin);
|
|
13975
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") return false;
|
|
13976
|
+
return isLoopbackHost(url.hostname);
|
|
13977
|
+
} catch {
|
|
13978
|
+
return false;
|
|
13979
|
+
}
|
|
13980
|
+
}
|
|
13792
13981
|
|
|
13793
13982
|
// src/server/router.ts
|
|
13794
|
-
var
|
|
13795
|
-
"Access-Control-Allow-Origin": "*",
|
|
13983
|
+
var CORS_BASE_HEADERS = {
|
|
13796
13984
|
"Access-Control-Allow-Methods": "GET, POST, DELETE, OPTIONS",
|
|
13797
13985
|
"Access-Control-Allow-Headers": "Content-Type, Authorization"
|
|
13798
13986
|
};
|
|
@@ -13845,9 +14033,31 @@ function jsonResponse(statusCode, body, extraHeaders) {
|
|
|
13845
14033
|
return {
|
|
13846
14034
|
statusCode,
|
|
13847
14035
|
body,
|
|
13848
|
-
headers: {
|
|
14036
|
+
headers: { "Content-Type": "application/json", ...extraHeaders }
|
|
14037
|
+
};
|
|
14038
|
+
}
|
|
14039
|
+
function corsHeadersFor(origin, explicitOrigins) {
|
|
14040
|
+
if (!origin || !isOriginAllowed(origin, explicitOrigins)) return {};
|
|
14041
|
+
return {
|
|
14042
|
+
"Access-Control-Allow-Origin": origin,
|
|
14043
|
+
Vary: "Origin",
|
|
14044
|
+
...CORS_BASE_HEADERS
|
|
13849
14045
|
};
|
|
13850
14046
|
}
|
|
14047
|
+
function isOriginAllowed(origin, explicitOrigins) {
|
|
14048
|
+
if (explicitOrigins && explicitOrigins.length > 0) {
|
|
14049
|
+
return explicitOrigins.includes("*") || explicitOrigins.includes(origin);
|
|
14050
|
+
}
|
|
14051
|
+
return isLoopbackOrigin(origin);
|
|
14052
|
+
}
|
|
14053
|
+
function isJsonContentType(contentType) {
|
|
14054
|
+
return contentType.toLowerCase().split(";")[0].trim() === "application/json";
|
|
14055
|
+
}
|
|
14056
|
+
function applyCorsHeaders(response, origin, corsOrigins) {
|
|
14057
|
+
const cors = corsHeadersFor(origin, corsOrigins);
|
|
14058
|
+
if (Object.keys(cors).length === 0) return response;
|
|
14059
|
+
return { ...response, headers: { ...cors, ...response.headers ?? {} } };
|
|
14060
|
+
}
|
|
13851
14061
|
function errorResponse(statusCode, error, message) {
|
|
13852
14062
|
return jsonResponse(statusCode, { error, message, statusCode });
|
|
13853
14063
|
}
|
|
@@ -13965,13 +14175,30 @@ async function ensureSession(sessionName, url, cdpEndpoint) {
|
|
|
13965
14175
|
function isHealthCheckPath(pathname) {
|
|
13966
14176
|
return pathname === "/api/v1/health";
|
|
13967
14177
|
}
|
|
13968
|
-
async function route(method, url, headers, body) {
|
|
14178
|
+
async function route(method, url, headers, body, corsOrigins) {
|
|
14179
|
+
const response = await routeInner(method, url, headers, body, corsOrigins);
|
|
14180
|
+
return applyCorsHeaders(response, headers.origin, corsOrigins);
|
|
14181
|
+
}
|
|
14182
|
+
async function routeInner(method, url, headers, body, corsOrigins) {
|
|
13969
14183
|
const parsedUrl = new URL(url, "http://localhost");
|
|
13970
14184
|
const pathname = parsedUrl.pathname;
|
|
13971
14185
|
const query = parseQueryString(parsedUrl.search);
|
|
13972
14186
|
if (method === "OPTIONS") {
|
|
13973
14187
|
return jsonResponse(204, null);
|
|
13974
14188
|
}
|
|
14189
|
+
if (headers.origin && !isOriginAllowed(headers.origin, corsOrigins)) {
|
|
14190
|
+
return errorResponse(403, "FORBIDDEN", `Origin ${headers.origin} is not allowed to call this API`);
|
|
14191
|
+
}
|
|
14192
|
+
if (method === "POST" || method === "PUT") {
|
|
14193
|
+
const contentType = headers["content-type"];
|
|
14194
|
+
if (contentType && !isJsonContentType(contentType)) {
|
|
14195
|
+
return errorResponse(
|
|
14196
|
+
415,
|
|
14197
|
+
"UNSUPPORTED_MEDIA_TYPE",
|
|
14198
|
+
`Content-Type must be application/json, got "${contentType}"`
|
|
14199
|
+
);
|
|
14200
|
+
}
|
|
14201
|
+
}
|
|
13975
14202
|
const match = matchRoute(method, pathname);
|
|
13976
14203
|
if (!match) {
|
|
13977
14204
|
const pathMatch = matchRoute("GET", pathname) || matchRoute("POST", pathname) || matchRoute("DELETE", pathname);
|
|
@@ -13994,19 +14221,24 @@ async function route(method, url, headers, body) {
|
|
|
13994
14221
|
return errorResponse(500, "INTERNAL_ERROR", errMsg(err));
|
|
13995
14222
|
}
|
|
13996
14223
|
}
|
|
13997
|
-
async function handleRequest(req, res, validateAuthFn) {
|
|
14224
|
+
async function handleRequest(req, res, validateAuthFn, corsOrigins) {
|
|
13998
14225
|
const url = req.url || "/";
|
|
13999
14226
|
const method = (req.method || "GET").toUpperCase();
|
|
14000
14227
|
const pathname = new URL(url, "http://localhost").pathname;
|
|
14001
14228
|
if (method === "OPTIONS") {
|
|
14002
|
-
const response2 = await route(method, url, headersToObject(req.headers), null);
|
|
14229
|
+
const response2 = await route(method, url, headersToObject(req.headers), null, corsOrigins);
|
|
14003
14230
|
writeResponse(res, response2);
|
|
14004
14231
|
return;
|
|
14005
14232
|
}
|
|
14006
14233
|
if (validateAuthFn && !isHealthCheckPath(pathname)) {
|
|
14007
14234
|
const authHeader = req.headers["authorization"];
|
|
14008
14235
|
if (!validateAuthFn(authHeader)) {
|
|
14009
|
-
|
|
14236
|
+
const denied = applyCorsHeaders(
|
|
14237
|
+
errorResponse(401, "UNAUTHORIZED", "Invalid or missing authentication token"),
|
|
14238
|
+
req.headers.origin,
|
|
14239
|
+
corsOrigins
|
|
14240
|
+
);
|
|
14241
|
+
writeResponse(res, denied);
|
|
14010
14242
|
return;
|
|
14011
14243
|
}
|
|
14012
14244
|
}
|
|
@@ -14014,7 +14246,7 @@ async function handleRequest(req, res, validateAuthFn) {
|
|
|
14014
14246
|
if (method === "POST" || method === "PUT") {
|
|
14015
14247
|
body = await readBody(req);
|
|
14016
14248
|
}
|
|
14017
|
-
const response = await route(method, url, headersToObject(req.headers), body);
|
|
14249
|
+
const response = await route(method, url, headersToObject(req.headers), body, corsOrigins);
|
|
14018
14250
|
writeResponse(res, response);
|
|
14019
14251
|
}
|
|
14020
14252
|
function headersToObject(headers) {
|
|
@@ -14055,26 +14287,35 @@ var HTTPServer = class {
|
|
|
14055
14287
|
host;
|
|
14056
14288
|
server = null;
|
|
14057
14289
|
validTokens;
|
|
14290
|
+
corsOrigins;
|
|
14058
14291
|
constructor(config) {
|
|
14059
14292
|
this.port = config?.port ?? 9224;
|
|
14060
|
-
this.host = config?.host ?? "
|
|
14293
|
+
this.host = config?.host ?? "127.0.0.1";
|
|
14061
14294
|
this.validTokens = resolveTokens(config?.tokens);
|
|
14295
|
+
this.corsOrigins = config?.corsOrigins;
|
|
14062
14296
|
}
|
|
14063
14297
|
/**
|
|
14064
14298
|
* Start the HTTP server and begin listening for requests.
|
|
14065
14299
|
*
|
|
14066
14300
|
* @returns The actual port and host the server bound to.
|
|
14067
|
-
* @throws If the server is already running
|
|
14301
|
+
* @throws If the server is already running, if binding a non-loopback
|
|
14302
|
+
* host without an auth token, or if the port cannot be bound.
|
|
14068
14303
|
*/
|
|
14069
14304
|
async start() {
|
|
14070
14305
|
if (this.server) {
|
|
14071
14306
|
throw new Error("HTTP server is already running");
|
|
14072
14307
|
}
|
|
14308
|
+
if (!isLoopbackHost(this.host) && !isAuthRequired(this.validTokens)) {
|
|
14309
|
+
throw new Error(
|
|
14310
|
+
`Refusing to start on non-loopback host "${this.host}" without an auth token \u2014 the API exposes session creation and command execution. Bind loopback only (the default, or --host 127.0.0.1), or provide a token via --token or XBROWSER_SERVER_TOKEN.`
|
|
14311
|
+
);
|
|
14312
|
+
}
|
|
14073
14313
|
const authRequired = isAuthRequired(this.validTokens);
|
|
14074
14314
|
const tokens = this.validTokens;
|
|
14315
|
+
const corsOrigins = this.corsOrigins;
|
|
14075
14316
|
this.server = createServer((req, res) => {
|
|
14076
14317
|
const authFn = authRequired ? (authHeader) => validateAuth(authHeader, tokens) : void 0;
|
|
14077
|
-
handleRequest(req, res, authFn).catch((err) => {
|
|
14318
|
+
handleRequest(req, res, authFn, corsOrigins).catch((err) => {
|
|
14078
14319
|
const message = err instanceof Error ? err.message : String(err);
|
|
14079
14320
|
if (!res.headersSent) {
|
|
14080
14321
|
res.writeHead(500, { "Content-Type": "application/json" });
|
|
@@ -14545,7 +14786,7 @@ async function routeCommand(argvIn, stdinCommands) {
|
|
|
14545
14786
|
replay: "replay <file> [--slow-mo <ms>] [--stop-on-error]",
|
|
14546
14787
|
create: "create <name> [--template static|dynamic|login|api]",
|
|
14547
14788
|
run: "run <file>",
|
|
14548
|
-
serve: "serve [--port <port>] [--token <token>]",
|
|
14789
|
+
serve: "serve [--port <port>] [--host <host>] [--token <token>] [--cors-origins <csv>]",
|
|
14549
14790
|
remote: "remote <url> [command] [--token <token>]",
|
|
14550
14791
|
convert: "convert <file> [--to js|py|sh]",
|
|
14551
14792
|
extract: "extract <file> [--format json|yaml]",
|
|
@@ -14822,7 +15063,7 @@ Run "xbrowser ${command} ${subCommand} --help" to see available parameters.`
|
|
|
14822
15063
|
const targetPage = pages[cmdTabIndex];
|
|
14823
15064
|
await targetPage.bringToFront().catch(() => {
|
|
14824
15065
|
});
|
|
14825
|
-
const { setActivePage: setActivePage2 } = await import("./browser-
|
|
15066
|
+
const { setActivePage: setActivePage2 } = await import("./browser-Q67BJTI2.js");
|
|
14826
15067
|
setActivePage2(session, targetPage);
|
|
14827
15068
|
}
|
|
14828
15069
|
}
|
|
@@ -14944,7 +15185,10 @@ Run "xbrowser ${command} ${subCommand} --help" to see available parameters.`
|
|
|
14944
15185
|
async function handleServe(_args, options, mode) {
|
|
14945
15186
|
const port = options.port ? Number(options.port) : void 0;
|
|
14946
15187
|
const token = options.token;
|
|
14947
|
-
const
|
|
15188
|
+
const host = options.host;
|
|
15189
|
+
const corsOriginsRaw = options["cors-origins"];
|
|
15190
|
+
const corsOrigins = corsOriginsRaw ? corsOriginsRaw.split(",").map((s) => s.trim()).filter(Boolean) : void 0;
|
|
15191
|
+
const httpServer = new HTTPServer({ port, host, tokens: token ? [token] : void 0, corsOrigins });
|
|
14948
15192
|
process.on("SIGINT", async () => {
|
|
14949
15193
|
await httpServer.stop();
|
|
14950
15194
|
return;
|
|
@@ -17995,7 +18239,7 @@ var DataCollector = class {
|
|
|
17995
18239
|
return results;
|
|
17996
18240
|
}
|
|
17997
18241
|
async createBrowserContext() {
|
|
17998
|
-
const { launch } = await import("./cdp-driver-
|
|
18242
|
+
const { launch } = await import("./cdp-driver-WWO2UZDR.js");
|
|
17999
18243
|
const { browser } = await launch({
|
|
18000
18244
|
headless: true,
|
|
18001
18245
|
args: ["--no-sandbox", "--disable-setuid-sandbox"]
|