@xbrowser/cli 1.10.0 → 1.12.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/dist/{anti-bot-DR56Y63V.js → anti-bot-GTTYNEFB.js} +1 -1
- package/dist/{browser-WQZ3D6AE.js → browser-3HQKDZQ6.js} +4 -2
- package/dist/{browser-T3V3JWVH.js → browser-ILXTEWH4.js} +1 -1
- package/dist/{browser-HBL72GPZ.js → browser-XPDMY2NX.js} +3 -3
- package/dist/{cdp-driver-J3YQ4LJV.js → cdp-driver-ESH3PDE6.js} +2 -1
- package/dist/cdp-driver-N2E2ZNSC.js +4644 -0
- package/dist/cdp-driver-YIKR4BUX.js +49 -0
- package/dist/chunk-3FWLW7FS.js +106 -0
- package/dist/chunk-A6LPGFAL.js +437 -0
- package/dist/{chunk-RRBXV7KE.js → chunk-DKM2JOZL.js} +633 -63
- package/dist/{chunk-ABXMBNQ6.js → chunk-H2A5JUK5.js} +37 -466
- package/dist/{chunk-5UGR6MUK.js → chunk-HBHOKZPN.js} +51 -16
- package/dist/{chunk-BNO7OKO4.js → chunk-HBMEFSTB.js} +39 -0
- package/dist/{chunk-INTQPBYF.js → chunk-JKVUFP3G.js} +6 -2
- package/dist/chunk-KJTABK3Z.js +1255 -0
- package/dist/{chunk-DWDXEGVK.js → chunk-NKW4A74J.js} +9 -3
- package/dist/{chunk-4452SPFI.js → chunk-NODRQGOK.js} +51 -16
- package/dist/{chunk-SQHSZENE.js → chunk-OMU63E6J.js} +3 -22
- package/dist/{chunk-NITFVWWS.js → chunk-OVW2UEWN.js} +981 -407
- package/dist/{chunk-WSCP7QCJ.js → chunk-R6IJ6PIV.js} +14 -11
- package/dist/{chunk-YMUSHPU4.js → chunk-SH6OTPXN.js} +46 -16
- package/dist/{chunk-MWFHZUIY.js → chunk-TEXCXIBW.js} +1 -1
- package/dist/{cdp-driver-2T4P4ZE2.js → chunk-Z6CUR3VG.js} +1542 -134
- package/dist/{chunk-IIM5GOD7.js → chunk-ZTHE5RBZ.js} +7 -1
- package/dist/cli.js +1136 -584
- package/dist/{daemon-client-GKEPT4NY.js → daemon-client-7D6EZNOE.js} +46 -16
- package/dist/{daemon-client-O6BYVRXV.js → daemon-client-HEGAXWGK.js} +1 -1
- package/dist/daemon-main.js +1031 -522
- package/dist/{human-interaction-5AO42MBA.js → human-interaction-4YR2N6R6.js} +2 -2
- package/dist/{human-interaction-C5OEGXGO.js → human-interaction-Y5IDH6LD.js} +1 -1
- package/dist/{human-interaction-ISXZTAYY.js → human-interaction-ZUTR5AC2.js} +2 -2
- package/dist/index.d.ts +10 -0
- package/dist/index.js +1147 -593
- package/dist/{proxy-C6CK3UH5.js → proxy-LUR4U5YF.js} +2 -1
- package/dist/{recovery-J2ISVGUL.js → recovery-FTZGV6VY.js} +2 -2
- package/dist/{recovery-ZJVVHP7N.js → recovery-L5GLDX4O.js} +1 -1
- package/dist/{recovery-EF33LKRJ.js → recovery-Z3RDZENA.js} +2 -2
- package/dist/{session-recorder-H3KEYU26.js → session-recorder-3BEVWHOK.js} +1 -1
- package/dist/{session-recorder-QRZMKFVL.js → session-recorder-SLDBENVF.js} +1 -1
- package/dist/{session-replayer-LJUC4TI7.js → session-replayer-K4A6OI4M.js} +90 -2
- package/package.json +1 -1
- package/dist/chunk-BAHSRZIX.js +0 -2191
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
detectAntiBot,
|
|
3
3
|
formatDetectionMessage
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-JKVUFP3G.js";
|
|
5
5
|
import {
|
|
6
6
|
forwardCommandLog,
|
|
7
7
|
forwardNetworkAnalyze,
|
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
forwardSessionList,
|
|
26
26
|
forwardViewerCheckSelector,
|
|
27
27
|
version
|
|
28
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-SH6OTPXN.js";
|
|
29
29
|
import {
|
|
30
30
|
buildViewerUrl
|
|
31
31
|
} from "./chunk-3OD76SUE.js";
|
|
@@ -41,8 +41,9 @@ import {
|
|
|
41
41
|
getMarketplaceUrl,
|
|
42
42
|
loadConfig,
|
|
43
43
|
resolveNpmPackageWithFallback,
|
|
44
|
+
resolveScreenshotsDir,
|
|
44
45
|
setConfigValue
|
|
45
|
-
} from "./chunk-
|
|
46
|
+
} from "./chunk-NKW4A74J.js";
|
|
46
47
|
import {
|
|
47
48
|
getDaemonConfig,
|
|
48
49
|
getDaemonProcessStatus,
|
|
@@ -66,7 +67,7 @@ import {
|
|
|
66
67
|
} from "./chunk-ANVL2ID2.js";
|
|
67
68
|
import {
|
|
68
69
|
SessionRecorder
|
|
69
|
-
} from "./chunk-
|
|
70
|
+
} from "./chunk-NODRQGOK.js";
|
|
70
71
|
import {
|
|
71
72
|
addKnownIssue,
|
|
72
73
|
getKnowledgePath,
|
|
@@ -91,15 +92,17 @@ import {
|
|
|
91
92
|
resolveLaunchOpts,
|
|
92
93
|
saveSessionDiskMeta,
|
|
93
94
|
setActivePage
|
|
94
|
-
} from "./chunk-
|
|
95
|
-
import "./chunk-
|
|
95
|
+
} from "./chunk-R6IJ6PIV.js";
|
|
96
|
+
import "./chunk-DKM2JOZL.js";
|
|
96
97
|
import "./chunk-TNEN6VQ2.js";
|
|
97
98
|
import {
|
|
98
99
|
errMsg
|
|
99
100
|
} from "./chunk-GDKLH7ZY.js";
|
|
100
101
|
import {
|
|
101
102
|
CDPInterceptorProxy,
|
|
102
|
-
advise
|
|
103
|
+
advise
|
|
104
|
+
} from "./chunk-A6LPGFAL.js";
|
|
105
|
+
import {
|
|
103
106
|
automationSignalsRule,
|
|
104
107
|
createRuleEngine,
|
|
105
108
|
domMutationRule,
|
|
@@ -110,15 +113,15 @@ import {
|
|
|
110
113
|
mouseTrajectoryRule,
|
|
111
114
|
networkAnomalyRule,
|
|
112
115
|
pageLifecycleRule
|
|
113
|
-
} from "./chunk-
|
|
116
|
+
} from "./chunk-H2A5JUK5.js";
|
|
114
117
|
import {
|
|
115
118
|
__require
|
|
116
119
|
} from "./chunk-KFQGP6VL.js";
|
|
117
120
|
|
|
118
121
|
// src/executor.ts
|
|
119
122
|
import {
|
|
120
|
-
ok as
|
|
121
|
-
fail as
|
|
123
|
+
ok as ok28,
|
|
124
|
+
fail as fail13,
|
|
122
125
|
isCommandResult,
|
|
123
126
|
CompositeStorage as CompositeStorage2,
|
|
124
127
|
TipCollector as TipCollector2,
|
|
@@ -491,14 +494,23 @@ var CAPTCHA_SELECTORS = {
|
|
|
491
494
|
generic: '#captcha, .captcha-container, [class*="captcha"]'
|
|
492
495
|
};
|
|
493
496
|
async function detectCaptcha(page) {
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
497
|
+
const hit = await page.evaluate(`
|
|
498
|
+
(function() {
|
|
499
|
+
var S = ${JSON.stringify(CAPTCHA_SELECTORS)};
|
|
500
|
+
for (var type in S) {
|
|
501
|
+
var sel = S[type];
|
|
502
|
+
var el;
|
|
503
|
+
try { el = document.querySelector(sel); } catch (e) { continue; }
|
|
504
|
+
if (el) {
|
|
505
|
+
var url = el.getAttribute && el.getAttribute('src');
|
|
506
|
+
return { type: type, selector: sel, iframeUrl: url || undefined };
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
return null;
|
|
510
|
+
})()
|
|
511
|
+
`).catch(() => null);
|
|
512
|
+
if (!hit) return null;
|
|
513
|
+
return { type: hit.type, selector: hit.selector, iframeUrl: hit.iframeUrl || void 0 };
|
|
502
514
|
}
|
|
503
515
|
async function waitForCaptchaSolved(page, captchaInfo, timeoutMs = 18e4) {
|
|
504
516
|
const startTime = Date.now();
|
|
@@ -549,12 +561,24 @@ var clickCommand = registerCommand({
|
|
|
549
561
|
let detectedNewPage;
|
|
550
562
|
let cleanup;
|
|
551
563
|
if (ctx.browserContext?.on) {
|
|
564
|
+
const targetIdOf = (p2) => p2._targetId;
|
|
565
|
+
let targetIdsBefore;
|
|
566
|
+
try {
|
|
567
|
+
targetIdsBefore = new Set(ctx.browserContext.pages().map(targetIdOf));
|
|
568
|
+
} catch {
|
|
569
|
+
targetIdsBefore = /* @__PURE__ */ new Set();
|
|
570
|
+
}
|
|
552
571
|
const pagePromise = new Promise((resolve10) => {
|
|
553
572
|
const timer = setTimeout(() => {
|
|
554
573
|
ctx.browserContext.off("page", handler);
|
|
555
574
|
resolve10(void 0);
|
|
556
575
|
}, 3e3);
|
|
557
576
|
const handler = (page2) => {
|
|
577
|
+
if (targetIdsBefore.has(targetIdOf(page2))) return;
|
|
578
|
+
try {
|
|
579
|
+
if ((page2.url?.() ?? "") === "about:blank") return;
|
|
580
|
+
} catch {
|
|
581
|
+
}
|
|
558
582
|
clearTimeout(timer);
|
|
559
583
|
ctx.browserContext.off("page", handler);
|
|
560
584
|
resolve10(page2);
|
|
@@ -955,9 +979,11 @@ var mouseCommand = registerCommand({
|
|
|
955
979
|
await ctx.page.mouse.move(p.x, p.y, { steps: p.steps || 1 });
|
|
956
980
|
break;
|
|
957
981
|
case "down":
|
|
982
|
+
await ctx.page.mouse.move(p.x, p.y);
|
|
958
983
|
await ctx.page.mouse.down({ button });
|
|
959
984
|
break;
|
|
960
985
|
case "up":
|
|
986
|
+
await ctx.page.mouse.move(p.x, p.y);
|
|
961
987
|
await ctx.page.mouse.up({ button });
|
|
962
988
|
break;
|
|
963
989
|
case "click":
|
|
@@ -971,28 +997,441 @@ var mouseCommand = registerCommand({
|
|
|
971
997
|
}
|
|
972
998
|
});
|
|
973
999
|
|
|
974
|
-
// src/commands/
|
|
1000
|
+
// src/commands/find-visual.ts
|
|
975
1001
|
import { z as z7 } from "zod";
|
|
976
|
-
import { ok as ok7,
|
|
1002
|
+
import { ok as ok7, fail as fail2 } from "@dyyz1993/xcli-core";
|
|
1003
|
+
import { readFileSync, existsSync } from "fs";
|
|
1004
|
+
import { homedir as homedir2 } from "os";
|
|
1005
|
+
import { join as join2 } from "path";
|
|
1006
|
+
function loadVLMCredentials() {
|
|
1007
|
+
const candidates = [
|
|
1008
|
+
process.env.XBROWSER_VLM_CONFIG,
|
|
1009
|
+
join2(homedir2(), ".zcode", "v2", "config.json")
|
|
1010
|
+
].filter(Boolean);
|
|
1011
|
+
for (const file of candidates) {
|
|
1012
|
+
try {
|
|
1013
|
+
if (!existsSync(file)) continue;
|
|
1014
|
+
const cfg = JSON.parse(readFileSync(file, "utf8"));
|
|
1015
|
+
const provider = Object.values(cfg.provider ?? {})[0];
|
|
1016
|
+
if (provider?.options?.apiKey && provider.options.baseURL) {
|
|
1017
|
+
const models = Object.keys(provider.models ?? {});
|
|
1018
|
+
const vl = models.find((m) => /vl|vision/i.test(m));
|
|
1019
|
+
const flash = models.find((m) => /flash/i.test(m) && /5\.?3/i.test(m));
|
|
1020
|
+
return {
|
|
1021
|
+
apiKey: provider.options.apiKey,
|
|
1022
|
+
baseURL: provider.options.baseURL.replace(/\/$/, ""),
|
|
1023
|
+
model: process.env.XBROWSER_VLM_MODEL || vl || flash || models.find((m) => /5\.[23]/.test(m)) || models[0]
|
|
1024
|
+
};
|
|
1025
|
+
}
|
|
1026
|
+
} catch {
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
return null;
|
|
1030
|
+
}
|
|
1031
|
+
async function locateByVLM(creds, imageBase64, target, feedback) {
|
|
1032
|
+
const prompt = `\u4F60\u5728\u4E00\u5F20\u7F51\u9875\u622A\u56FE\u4E0A\u5B9A\u4F4D UI \u5143\u7D20\u3002\u76EE\u6807\u5143\u7D20\u63CF\u8FF0\uFF1A"""${target}"""
|
|
1033
|
+
|
|
1034
|
+
\u4E25\u683C\u53EA\u8F93\u51FA\u4E00\u4E2A JSON \u5BF9\u8C61\uFF08\u65E0 markdown\u3001\u65E0\u89E3\u91CA\uFF09\uFF1A
|
|
1035
|
+
{"label":"<\u5143\u7D20\u540D>","box":{"x":<\u5DE6\u4E0Ax>,"y":<\u5DE6\u4E0Ay>,"width":<\u5BBD>,"height":<\u9AD8>},"confidence":"high|medium|low"}
|
|
1036
|
+
\u5750\u6807\u4EE5\u622A\u56FE\u5DE6\u4E0A\u89D2\u4E3A\u539F\u70B9\u3001\u5355\u4F4D\u4E3A\u50CF\u7D20\u3002\u627E\u4E0D\u5230\u5143\u7D20\u5219\u8F93\u51FA {"label":null}\u3002
|
|
1037
|
+
${feedback ? `\u6CE8\u610F\uFF1A${feedback}` : ""}`;
|
|
1038
|
+
const resp = await fetch(creds.baseURL + "/v1/messages", {
|
|
1039
|
+
method: "POST",
|
|
1040
|
+
headers: { "content-type": "application/json", "x-api-key": creds.apiKey, "anthropic-version": "2023-06-01" },
|
|
1041
|
+
body: JSON.stringify({
|
|
1042
|
+
model: creds.model,
|
|
1043
|
+
max_tokens: 2e3,
|
|
1044
|
+
// thinking 模型的思考块也计入配额,300 会在 text 前截断
|
|
1045
|
+
messages: [{
|
|
1046
|
+
role: "user",
|
|
1047
|
+
content: [
|
|
1048
|
+
{ type: "image", source: { type: "base64", media_type: "image/png", data: imageBase64 } },
|
|
1049
|
+
{ type: "text", text: prompt }
|
|
1050
|
+
]
|
|
1051
|
+
}]
|
|
1052
|
+
}),
|
|
1053
|
+
signal: AbortSignal.timeout(6e4)
|
|
1054
|
+
});
|
|
1055
|
+
if (!resp.ok) throw new Error(`VLM HTTP ${resp.status}`);
|
|
1056
|
+
const data = await resp.json();
|
|
1057
|
+
const text = (data.content ?? []).filter((c) => c.type === "text").map((c) => c.text ?? "").join("");
|
|
1058
|
+
const m = text.match(/\{[\s\S]*\}/);
|
|
1059
|
+
if (!m) {
|
|
1060
|
+
console.error("[find-visual] VLM raw (no JSON):", text.substring(0, 200));
|
|
1061
|
+
return null;
|
|
1062
|
+
}
|
|
1063
|
+
const hit = JSON.parse(m[0]);
|
|
1064
|
+
if (!hit.label || !hit.box) return null;
|
|
1065
|
+
return hit;
|
|
1066
|
+
}
|
|
1067
|
+
var findVisualCommand = registerCommand({
|
|
1068
|
+
name: "find-visual",
|
|
1069
|
+
description: "Visual element location: screenshot \u2192 VLM \u2192 page coordinates (optionally click)",
|
|
1070
|
+
scope: "page",
|
|
1071
|
+
parameters: z7.object({
|
|
1072
|
+
element: z7.string().describe('\u5143\u7D20\u7684\u81EA\u7136\u8BED\u8A00\u63CF\u8FF0\uFF0C\u5982 "\u7EA2\u8272\u7684\u767B\u5F55\u6309\u94AE"\uFF08\u6CE8\u610F\uFF1A--target \u662F\u5168\u5C40\u9875\u9762\u8DEF\u7531\u53C2\u6570\uFF0C\u6B64\u5904\u7528 --element\uFF09'),
|
|
1073
|
+
click: z7.boolean().optional().describe("\u627E\u5230\u540E\u7ACB\u5373\u70B9\u51FB\uFF08\u771F\u5B9E\u9F20\u6807\u4E8B\u4EF6\uFF09"),
|
|
1074
|
+
threshold: z7.coerce.number().optional().describe("\u7F6E\u4FE1\u5EA6\u9608\u503C\uFF0C\u4F4E\u4E8E\u5219\u62A5 not-found\uFF08\u9ED8\u8BA4 0\uFF09"),
|
|
1075
|
+
zoom: z7.boolean().optional().describe("\u4E8C\u6BB5\u5F0F\u653E\u5927\u7CBE\u4FEE\uFF08\u9ED8\u8BA4\u5F00\uFF09\u2014\u2014\u7C97\u6846\u533A\u57DF\u4E8C\u6B21\u622A\u56FE\u8BA9 VLM \u7CBE\u786E\u5B9A\u4F4D")
|
|
1076
|
+
}),
|
|
1077
|
+
result: z7.object({
|
|
1078
|
+
found: z7.boolean(),
|
|
1079
|
+
element: z7.string(),
|
|
1080
|
+
box: z7.unknown().optional(),
|
|
1081
|
+
center: z7.unknown().optional(),
|
|
1082
|
+
confidence: z7.string().optional(),
|
|
1083
|
+
model: z7.string().optional(),
|
|
1084
|
+
clicked: z7.boolean().optional(),
|
|
1085
|
+
screenshot: z7.string().optional()
|
|
1086
|
+
}),
|
|
1087
|
+
handler: async (p, ctx) => {
|
|
1088
|
+
const page = ctx.page;
|
|
1089
|
+
const creds = loadVLMCredentials();
|
|
1090
|
+
if (!creds) {
|
|
1091
|
+
return fail2("\u672A\u627E\u5230 VLM \u51ED\u636E\uFF1A\u9700\u8981 ~/.zcode/v2/config.json \u7684 provider \u914D\u7F6E\u6216 XBROWSER_VLM_CONFIG \u73AF\u5883\u53D8\u91CF", [
|
|
1092
|
+
"\u4E5F\u53EF\u7528 XBROWSER_VLM_MODEL \u6307\u5B9A\u6A21\u578B\uFF08\u9ED8\u8BA4\u81EA\u52A8\u9009 VL/\u65D7\u8230\u6A21\u578B\uFF09"
|
|
1093
|
+
]);
|
|
1094
|
+
}
|
|
1095
|
+
const buffer = await page.screenshot({ type: "png" });
|
|
1096
|
+
const imageBase64 = buffer.toString("base64");
|
|
1097
|
+
const imgW = buffer.readUInt32BE(16);
|
|
1098
|
+
const imgH = buffer.readUInt32BE(20);
|
|
1099
|
+
let hit = null;
|
|
1100
|
+
let feedback = "";
|
|
1101
|
+
try {
|
|
1102
|
+
for (let attempt = 0; attempt < 3 && !hit; attempt++) {
|
|
1103
|
+
const candidate = await locateByVLM(creds, imageBase64, p.element, feedback);
|
|
1104
|
+
if (!candidate) break;
|
|
1105
|
+
const b = candidate.box;
|
|
1106
|
+
const inBounds = b.x >= 0 && b.y >= 0 && b.x + b.width <= imgW + 2 && b.y + b.height <= imgH + 2;
|
|
1107
|
+
const sane = b.width > 2 && b.height > 2;
|
|
1108
|
+
if (inBounds && sane) {
|
|
1109
|
+
hit = candidate;
|
|
1110
|
+
break;
|
|
1111
|
+
}
|
|
1112
|
+
feedback = `\u4E0A\u6B21\u8F93\u51FA box=${JSON.stringify(b)} \u65E0\u6548\uFF08${!inBounds ? "\u8D85\u51FA\u56FE\u50CF\u8FB9\u754C" : "\u5C3A\u5BF8\u8FC7\u5C0F"}\uFF09\u3002\u8BF7\u57FA\u4E8E\u622A\u56FE\u91CD\u65B0\u4ED4\u7EC6\u5B9A\u4F4D\uFF0C\u5750\u6807\u539F\u70B9\u5728\u56FE\u50CF\u5DE6\u4E0A\u89D2\u3002`;
|
|
1113
|
+
}
|
|
1114
|
+
} catch (e) {
|
|
1115
|
+
return fail2(`VLM \u8C03\u7528\u5931\u8D25: ${e instanceof Error ? e.message : String(e)}`);
|
|
1116
|
+
}
|
|
1117
|
+
if (!hit) {
|
|
1118
|
+
return ok7({ found: false, element: p.element, model: creds.model }, [
|
|
1119
|
+
`\u622A\u56FE\u4E2D\u672A\u627E\u5230: ${p.element}`
|
|
1120
|
+
]);
|
|
1121
|
+
}
|
|
1122
|
+
if (p.zoom !== false && hit) {
|
|
1123
|
+
try {
|
|
1124
|
+
const pad = Math.max(20, Math.round(Math.max(hit.box.width, hit.box.height) * 0.5));
|
|
1125
|
+
const clip = {
|
|
1126
|
+
x: Math.max(0, hit.box.x - pad),
|
|
1127
|
+
y: Math.max(0, hit.box.y - pad),
|
|
1128
|
+
width: Math.min(imgW - Math.max(0, hit.box.x - pad), hit.box.width + pad * 2),
|
|
1129
|
+
height: Math.min(imgH - Math.max(0, hit.box.y - pad), hit.box.height + pad * 2)
|
|
1130
|
+
};
|
|
1131
|
+
if (clip.width > 8 && clip.height > 8) {
|
|
1132
|
+
const zoomBuf = await page.screenshot({ type: "png", clip });
|
|
1133
|
+
const zoomB64 = zoomBuf.toString("base64");
|
|
1134
|
+
const zoomW = zoomBuf.readUInt32BE(16);
|
|
1135
|
+
const zoomH = zoomBuf.readUInt32BE(20);
|
|
1136
|
+
const refined = await locateByVLM(creds, zoomB64, p.element);
|
|
1137
|
+
if (refined) {
|
|
1138
|
+
const rb = refined.box;
|
|
1139
|
+
const inZoom = rb.x >= 0 && rb.y >= 0 && rb.x + rb.width <= zoomW + 2 && rb.y + rb.height <= zoomH + 2 && rb.width > 2 && rb.height > 2;
|
|
1140
|
+
if (inZoom) {
|
|
1141
|
+
hit = {
|
|
1142
|
+
label: refined.label || hit.label,
|
|
1143
|
+
box: {
|
|
1144
|
+
x: clip.x + rb.x,
|
|
1145
|
+
y: clip.y + rb.y,
|
|
1146
|
+
width: rb.width,
|
|
1147
|
+
height: rb.height
|
|
1148
|
+
},
|
|
1149
|
+
confidence: refined.confidence === "low" ? hit.confidence : refined.confidence
|
|
1150
|
+
};
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
} catch {
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
if (p.threshold !== void 0) {
|
|
1158
|
+
const rank = { high: 3, medium: 2, low: 1 };
|
|
1159
|
+
const thresholdName = p.threshold >= 3 ? "high" : p.threshold >= 2 ? "medium" : "low";
|
|
1160
|
+
if ((rank[hit.confidence ?? "low"] ?? 1) < (rank[thresholdName] ?? 1)) {
|
|
1161
|
+
return ok7({ found: false, element: p.element, confidence: hit.confidence, box: hit.box, model: creds.model }, [
|
|
1162
|
+
`\u7F6E\u4FE1\u5EA6 ${hit.confidence} \u4F4E\u4E8E\u9608\u503C ${p.threshold}`
|
|
1163
|
+
]);
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
const viewport = await page.evaluate("JSON.stringify({w: window.innerWidth, h: window.innerHeight})").then((v) => JSON.parse(String(v))).catch(() => ({ w: 1280, h: 800 }));
|
|
1167
|
+
const pngW = buffer.readUInt32BE(16);
|
|
1168
|
+
const pngH = buffer.readUInt32BE(20);
|
|
1169
|
+
const scaleX = viewport.w / pngW;
|
|
1170
|
+
const scaleY = viewport.h / pngH;
|
|
1171
|
+
const center = {
|
|
1172
|
+
x: Math.round((hit.box.x + hit.box.width / 2) * scaleX),
|
|
1173
|
+
y: Math.round((hit.box.y + hit.box.height / 2) * scaleY)
|
|
1174
|
+
};
|
|
1175
|
+
let clicked = false;
|
|
1176
|
+
if (p.click) {
|
|
1177
|
+
await page.mouse.click(center.x, center.y, { stealth: true });
|
|
1178
|
+
clicked = true;
|
|
1179
|
+
}
|
|
1180
|
+
return ok7({
|
|
1181
|
+
found: true,
|
|
1182
|
+
element: p.element,
|
|
1183
|
+
box: hit.box,
|
|
1184
|
+
center,
|
|
1185
|
+
confidence: hit.confidence,
|
|
1186
|
+
model: creds.model,
|
|
1187
|
+
clicked,
|
|
1188
|
+
screenshot: `data:image/png;base64,${imageBase64.substring(0, 64)}... (${Math.round(imageBase64.length / 1024)}KB)`
|
|
1189
|
+
}, [
|
|
1190
|
+
clicked ? `\u5DF2\u70B9\u51FB (${center.x}, ${center.y}) \u2014 ${hit.label} [${hit.confidence}]` : `\u5B9A\u4F4D\u5230 "${hit.label}" \u4E2D\u5FC3 (${center.x}, ${center.y}) [${hit.confidence}] \u2014 \u52A0 --click \u6267\u884C\u70B9\u51FB`
|
|
1191
|
+
]);
|
|
1192
|
+
}
|
|
1193
|
+
});
|
|
1194
|
+
|
|
1195
|
+
// src/commands/vision-task.ts
|
|
1196
|
+
import { z as z8 } from "zod";
|
|
1197
|
+
import { ok as ok8, fail as fail3 } from "@dyyz1993/xcli-core";
|
|
1198
|
+
import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
|
|
1199
|
+
import { homedir as homedir3 } from "os";
|
|
1200
|
+
import { join as join3 } from "path";
|
|
1201
|
+
function loadVLMCredentials2() {
|
|
1202
|
+
const candidates = [
|
|
1203
|
+
process.env.XBROWSER_VLM_CONFIG,
|
|
1204
|
+
join3(homedir3(), ".zcode", "v2", "config.json")
|
|
1205
|
+
].filter(Boolean);
|
|
1206
|
+
for (const file of candidates) {
|
|
1207
|
+
try {
|
|
1208
|
+
if (!existsSync2(file)) continue;
|
|
1209
|
+
const cfg = JSON.parse(readFileSync2(file, "utf8"));
|
|
1210
|
+
const provider = Object.values(cfg.provider ?? {})[0];
|
|
1211
|
+
if (provider?.options?.apiKey && provider.options.baseURL) {
|
|
1212
|
+
const models = Object.keys(provider.models ?? {});
|
|
1213
|
+
const vl = models.find((m) => /vl|vision/i.test(m));
|
|
1214
|
+
const flash = models.find((m) => /flash/i.test(m) && /5\.?3/i.test(m));
|
|
1215
|
+
return {
|
|
1216
|
+
apiKey: provider.options.apiKey,
|
|
1217
|
+
baseURL: provider.options.baseURL.replace(/\/$/, ""),
|
|
1218
|
+
model: process.env.XBROWSER_VLM_MODEL || vl || flash || models.find((m) => /5\.[23]/.test(m)) || models[0]
|
|
1219
|
+
};
|
|
1220
|
+
}
|
|
1221
|
+
} catch {
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
return null;
|
|
1225
|
+
}
|
|
1226
|
+
async function vlmAsk(creds, content, maxTokens = 2e3) {
|
|
1227
|
+
const resp = await fetch(creds.baseURL + "/v1/messages", {
|
|
1228
|
+
method: "POST",
|
|
1229
|
+
headers: { "content-type": "application/json", "x-api-key": creds.apiKey, "anthropic-version": "2023-06-01" },
|
|
1230
|
+
body: JSON.stringify({ model: creds.model, max_tokens: maxTokens, messages: [{ role: "user", content }] }),
|
|
1231
|
+
signal: AbortSignal.timeout(9e4)
|
|
1232
|
+
});
|
|
1233
|
+
if (!resp.ok) throw new Error(`VLM HTTP ${resp.status}`);
|
|
1234
|
+
const data = await resp.json();
|
|
1235
|
+
return (data.content ?? []).filter((c) => c.type === "text").map((c) => c.text ?? "").join("");
|
|
1236
|
+
}
|
|
1237
|
+
async function vlmAskRetry(creds, content, maxTokens = 2e3) {
|
|
1238
|
+
let last = "";
|
|
1239
|
+
for (let i = 0; i < 2; i++) {
|
|
1240
|
+
last = await vlmAsk(creds, content, maxTokens);
|
|
1241
|
+
if (last.trim()) return last;
|
|
1242
|
+
await new Promise((r) => setTimeout(r, 1200));
|
|
1243
|
+
}
|
|
1244
|
+
return last;
|
|
1245
|
+
}
|
|
1246
|
+
function pngDims(b64) {
|
|
1247
|
+
const buf = Buffer.from(b64, "base64");
|
|
1248
|
+
return { w: buf.readUInt32BE(16), h: buf.readUInt32BE(20) };
|
|
1249
|
+
}
|
|
1250
|
+
function parseDecision(text) {
|
|
1251
|
+
const m = text.match(/\{[\s\S]*\}/);
|
|
1252
|
+
if (!m) return null;
|
|
1253
|
+
try {
|
|
1254
|
+
const j = JSON.parse(m[0]);
|
|
1255
|
+
if (!j.action || !j.action.type) return null;
|
|
1256
|
+
return j;
|
|
1257
|
+
} catch {
|
|
1258
|
+
return null;
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
var SYSTEM_PROMPT = `\u4F60\u662F\u6D4F\u89C8\u5668\u89C6\u89C9\u64CD\u4F5C Agent\u3002\u6839\u636E\u622A\u56FE\u548C\u4EFB\u52A1\u5386\u53F2\uFF0C\u51B3\u5B9A\u4E0B\u4E00\u4E2A\u52A8\u4F5C\u3002
|
|
1262
|
+
|
|
1263
|
+
\u4E25\u683C\u53EA\u8F93\u51FA\u4E00\u4E2A JSON\uFF08\u65E0 markdown\uFF09\uFF1A
|
|
1264
|
+
{"thought":"<\u7B80\u77ED\u63A8\u7406>","action":{"type":"click","x":<int>,"y":<int>}
|
|
1265
|
+
\u6216 {"type":"type","text":"<\u8981\u8F93\u5165\u7684\u6587\u5B57>"}\uFF08\u8F93\u5165\u524D\u987B\u5DF2\u70B9\u51FB\u805A\u7126\u8F93\u5165\u6846\uFF09
|
|
1266
|
+
\u6216 {"type":"key","key":"Enter"}\uFF08key \u540D\uFF1AEnter/Tab/Escape/ArrowDown\u2026\uFF09
|
|
1267
|
+
\u6216 {"type":"scroll","direction":"down","amount":<px>}
|
|
1268
|
+
\u6216 {"type":"done","summary":"<\u4EFB\u52A1\u5B8C\u6210\u6458\u8981>"}
|
|
1269
|
+
\u6216 {"type":"stuck","reason":"<\u65E0\u6CD5\u7EE7\u7EED\u7684\u539F\u56E0>"}}
|
|
1270
|
+
|
|
1271
|
+
\u5750\u6807\u4EE5\u622A\u56FE\u5DE6\u4E0A\u89D2\u4E3A\u539F\u70B9\u3001\u50CF\u7D20\u4E3A\u5355\u4F4D\u3002\u89C4\u5219\uFF1A
|
|
1272
|
+
1. \u6BCF\u6B21\u53EA\u505A\u4E00\u4E2A\u52A8\u4F5C\uFF1B\u8F93\u5165\u6587\u5B57\u524D\u786E\u8BA4\u8F93\u5165\u6846\u5DF2\u805A\u7126\uFF08\u4E0A\u4E00\u6B65\u662F click \u8F93\u5165\u6846\uFF09
|
|
1273
|
+
2. \u9875\u9762\u65E0\u660E\u663E\u53D8\u5316\u65F6\u6362\u7B56\u7565\uFF0C\u4E0D\u8981\u91CD\u590D\u540C\u4E00\u52A8\u4F5C\u8D85\u8FC7 2 \u6B21
|
|
1274
|
+
3. \u4EFB\u52A1\u76EE\u6807\u8FBE\u6210\u7ACB\u5373\u8F93\u51FA done\uFF1B5 \u6B65\u65E0\u8FDB\u5C55\u8F93\u51FA stuck`;
|
|
1275
|
+
var visionTaskCommand = registerCommand({
|
|
1276
|
+
name: "vision-task",
|
|
1277
|
+
description: "Vision-loop agent: screenshot \u2192 VLM decide \u2192 CDP act \u2192 verify, until done",
|
|
1278
|
+
scope: "page",
|
|
1279
|
+
parameters: z8.object({
|
|
1280
|
+
task: z8.string().describe('\u81EA\u7136\u8BED\u8A00\u4EFB\u52A1\uFF0C\u5982 "\u641C\u7D22 xbrowser \u5E76\u6253\u5F00\u7B2C\u4E00\u7BC7\u6587\u7AE0"'),
|
|
1281
|
+
maxSteps: z8.coerce.number().optional().describe("\u6700\u5927\u6B65\u6570\uFF08\u9ED8\u8BA4 12\uFF09"),
|
|
1282
|
+
settleMs: z8.coerce.number().optional().describe("\u6BCF\u6B65\u540E\u7B49\u5F85\u9875\u9762\u7A33\u5B9A\u7684\u6BEB\u79D2\u6570\uFF08\u9ED8\u8BA4 1500\uFF09")
|
|
1283
|
+
}),
|
|
1284
|
+
result: z8.object({
|
|
1285
|
+
done: z8.boolean(),
|
|
1286
|
+
steps: z8.unknown().optional(),
|
|
1287
|
+
summary: z8.string().optional(),
|
|
1288
|
+
model: z8.string().optional()
|
|
1289
|
+
}),
|
|
1290
|
+
handler: async (p, ctx) => {
|
|
1291
|
+
let page = ctx.page;
|
|
1292
|
+
const creds = loadVLMCredentials2();
|
|
1293
|
+
if (!creds) {
|
|
1294
|
+
return fail3("\u672A\u627E\u5230 VLM \u51ED\u636E\uFF1A\u9700\u8981 ~/.zcode/v2/config.json \u6216 XBROWSER_VLM_CONFIG");
|
|
1295
|
+
}
|
|
1296
|
+
const maxSteps = p.maxSteps ?? 12;
|
|
1297
|
+
const settleMs = p.settleMs ?? 1500;
|
|
1298
|
+
const steps = [];
|
|
1299
|
+
const history = [];
|
|
1300
|
+
let lastUrl = page.url();
|
|
1301
|
+
for (let step = 1; step <= maxSteps; step++) {
|
|
1302
|
+
if (page.url() === "about:blank") {
|
|
1303
|
+
try {
|
|
1304
|
+
const ctxObj = page.context?.();
|
|
1305
|
+
const pages = ctxObj?.pages?.() || [];
|
|
1306
|
+
let real;
|
|
1307
|
+
for (let i = pages.length - 1; i >= 0; i--) {
|
|
1308
|
+
const u = pages[i]?.url?.() ?? "";
|
|
1309
|
+
if (u && u !== "about:blank") {
|
|
1310
|
+
real = pages[i];
|
|
1311
|
+
break;
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
if (real) {
|
|
1315
|
+
await real.bringToFront().catch(() => {
|
|
1316
|
+
});
|
|
1317
|
+
page = real;
|
|
1318
|
+
}
|
|
1319
|
+
} catch {
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
const buffer = await page.screenshot({ type: "png" });
|
|
1323
|
+
const b64 = buffer.toString("base64");
|
|
1324
|
+
const { w, h } = pngDims(b64);
|
|
1325
|
+
const obsText = [
|
|
1326
|
+
`\u4EFB\u52A1\uFF1A${p.task}`,
|
|
1327
|
+
`\u622A\u56FE\u5C3A\u5BF8\uFF1A${w}x${h}\uFF08\u5750\u6807\u539F\u70B9\uFF1A\u5DE6\u4E0A\u89D2\uFF09`,
|
|
1328
|
+
`\u5F53\u524D URL\uFF1A${page.url()}`,
|
|
1329
|
+
history.length ? `\u5DF2\u6267\u884C\u52A8\u4F5C\uFF1A
|
|
1330
|
+
${history.slice(-6).join("\n")}` : "\u5C1A\u65E0\u52A8\u4F5C\u3002"
|
|
1331
|
+
].join("\n");
|
|
1332
|
+
const reply = await vlmAskRetry(creds, [
|
|
1333
|
+
{ type: "image", source: { type: "base64", media_type: "image/png", data: b64 } },
|
|
1334
|
+
{ type: "text", text: `${SYSTEM_PROMPT}
|
|
1335
|
+
|
|
1336
|
+
${obsText}` }
|
|
1337
|
+
]);
|
|
1338
|
+
const decision = parseDecision(reply);
|
|
1339
|
+
if (!decision) {
|
|
1340
|
+
steps.push({ n: step, thought: "(unparseable)", action: "stop", ts: Date.now() });
|
|
1341
|
+
return ok8({ done: false, steps, summary: `\u7B2C ${step} \u6B65 VLM \u8F93\u51FA\u65E0\u6CD5\u89E3\u6790: ${reply.substring(0, 120)}`, model: creds.model });
|
|
1342
|
+
}
|
|
1343
|
+
const a = decision.action;
|
|
1344
|
+
let acted = "";
|
|
1345
|
+
try {
|
|
1346
|
+
if (a.type === "done") {
|
|
1347
|
+
steps.push({ n: step, thought: decision.thought, action: "done", ts: Date.now() });
|
|
1348
|
+
return ok8({ done: true, steps, summary: a.summary, model: creds.model });
|
|
1349
|
+
}
|
|
1350
|
+
if (a.type === "stuck") {
|
|
1351
|
+
steps.push({ n: step, thought: decision.thought, action: "stuck", ts: Date.now() });
|
|
1352
|
+
return ok8({ done: false, steps, summary: `stuck: ${a.reason}`, model: creds.model });
|
|
1353
|
+
}
|
|
1354
|
+
if (a.type === "click") {
|
|
1355
|
+
const cx = Math.max(2, Math.min(w - 2, Math.round(a.x)));
|
|
1356
|
+
const cy = Math.max(2, Math.min(h - 2, Math.round(a.y)));
|
|
1357
|
+
await page.mouse.click(cx, cy, { stealth: true });
|
|
1358
|
+
acted = `click(${cx},${cy})`;
|
|
1359
|
+
} else if (a.type === "type") {
|
|
1360
|
+
await page.keyboard.type(a.text, { stealth: true });
|
|
1361
|
+
acted = `type("${a.text.substring(0, 30)}")`;
|
|
1362
|
+
} else if (a.type === "key") {
|
|
1363
|
+
await page.keyboard.press(a.key);
|
|
1364
|
+
acted = `key(${a.key})`;
|
|
1365
|
+
} else if (a.type === "scroll") {
|
|
1366
|
+
await page.mouse.wheel(0, a.direction === "down" ? a.amount : -a.amount);
|
|
1367
|
+
acted = `scroll(${a.direction},${a.amount})`;
|
|
1368
|
+
}
|
|
1369
|
+
} catch (e) {
|
|
1370
|
+
acted = `${a.type} FAILED: ${e instanceof Error ? e.message : String(e).substring(0, 80)}`;
|
|
1371
|
+
}
|
|
1372
|
+
steps.push({ n: step, thought: decision.thought, action: acted, ts: Date.now() });
|
|
1373
|
+
history.push(`${step}. ${acted} \u2014 ${decision.thought.substring(0, 60)}`);
|
|
1374
|
+
await new Promise((r) => setTimeout(r, settleMs));
|
|
1375
|
+
if (a.type === "click") {
|
|
1376
|
+
try {
|
|
1377
|
+
const ctxObj = page.context?.();
|
|
1378
|
+
const pages = ctxObj?.pages?.() || [];
|
|
1379
|
+
for (let i = pages.length - 1; i >= 0; i--) {
|
|
1380
|
+
const cand = pages[i];
|
|
1381
|
+
if (!cand || cand === page) continue;
|
|
1382
|
+
const u = cand.url?.() ?? "";
|
|
1383
|
+
if (!u || u === "about:blank") continue;
|
|
1384
|
+
if (u === page.url()) break;
|
|
1385
|
+
await cand.bringToFront().catch(() => {
|
|
1386
|
+
});
|
|
1387
|
+
page = cand;
|
|
1388
|
+
break;
|
|
1389
|
+
}
|
|
1390
|
+
} catch {
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
const urlNow = page.url();
|
|
1394
|
+
if (urlNow !== lastUrl) {
|
|
1395
|
+
history.push(` [\u9875\u9762\u8DF3\u8F6C \u2192 ${urlNow.substring(0, 80)}]`);
|
|
1396
|
+
lastUrl = urlNow;
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
return ok8({ done: false, steps, summary: `\u5DF2\u8FBE\u6700\u5927\u6B65\u6570 ${maxSteps}`, model: creds.model });
|
|
1400
|
+
}
|
|
1401
|
+
});
|
|
1402
|
+
|
|
1403
|
+
// src/commands/evaluate.ts
|
|
1404
|
+
import { z as z9 } from "zod";
|
|
1405
|
+
import { ok as ok9, normalizeTips as normalizeTips2 } from "@dyyz1993/xcli-core";
|
|
977
1406
|
var evaluateCommand = registerCommand({
|
|
978
1407
|
name: "eval",
|
|
979
|
-
description: "Evaluate JavaScript expression in the browser",
|
|
1408
|
+
description: "Evaluate JavaScript expression in the browser (--frame <url-substring> \u53EF\u5B9A\u5411\u5230 iframe \u4E0A\u4E0B\u6587\uFF0C\u8DE8\u57DF iframe \u4EA6\u53EF)",
|
|
980
1409
|
scope: "page",
|
|
981
|
-
parameters:
|
|
982
|
-
expression:
|
|
1410
|
+
parameters: z9.object({
|
|
1411
|
+
expression: z9.string().optional(),
|
|
1412
|
+
frame: z9.string().optional().describe("\u5728 URL \u542B\u6B64\u5B50\u4E32\u7684 iframe \u91CC\u6267\u884C\uFF08\u540C\u8FDB\u7A0B\u7528 contextId\uFF0C\u8DE8\u57DF OOPIF \u7528 Target auto-attach\uFF09"),
|
|
1413
|
+
// Internal: base64url-encoded script set by the router for `eval <js...>`,
|
|
1414
|
+
// so multiline/semicolon JS survives chain parsing (which splits on ';'
|
|
1415
|
+
// and whitespace). Not intended for direct CLI use.
|
|
1416
|
+
"script-b64": z9.string().optional().describe("Internal: base64url script set by the CLI router; do not pass directly")
|
|
983
1417
|
}),
|
|
984
1418
|
handler: async (p, ctx) => {
|
|
1419
|
+
const rawScript = p["script-b64"];
|
|
1420
|
+
const expression = rawScript ? Buffer.from(rawScript, "base64url").toString("utf8") : p.expression;
|
|
1421
|
+
if (!expression || !expression.trim()) {
|
|
1422
|
+
return { ok: false, error: "eval: expression is required" };
|
|
1423
|
+
}
|
|
985
1424
|
const engine = createRuleEngine();
|
|
986
1425
|
engine.start();
|
|
987
1426
|
const decision = engine.evaluate({
|
|
988
1427
|
method: "Runtime.evaluate",
|
|
989
|
-
params: { expression
|
|
1428
|
+
params: { expression },
|
|
990
1429
|
sessionId: "eval-check",
|
|
991
1430
|
direction: "client\u2192browser"
|
|
992
1431
|
});
|
|
993
1432
|
engine.stop();
|
|
994
|
-
const result = await ctx.page.
|
|
995
|
-
const response =
|
|
1433
|
+
const result = p.frame ? await ctx.page.evaluateInFrame(p.frame, expression) : await ctx.page.evaluate(expression);
|
|
1434
|
+
const response = ok9({ result });
|
|
996
1435
|
if (decision && decision.severity === "danger") {
|
|
997
1436
|
response.tips = normalizeTips2([`${decision.suggestion}`]);
|
|
998
1437
|
}
|
|
@@ -1000,37 +1439,65 @@ var evaluateCommand = registerCommand({
|
|
|
1000
1439
|
}
|
|
1001
1440
|
});
|
|
1002
1441
|
|
|
1442
|
+
// src/commands/ua.ts
|
|
1443
|
+
import { z as z10 } from "zod";
|
|
1444
|
+
import { ok as ok10 } from "@dyyz1993/xcli-core";
|
|
1445
|
+
function uaHookScript(ua) {
|
|
1446
|
+
const uaJson = JSON.stringify(ua);
|
|
1447
|
+
const appVersion = JSON.stringify(ua.replace(/^Mozilla\//, ""));
|
|
1448
|
+
return `try{Object.defineProperty(navigator,'userAgent',{get:()=>${uaJson},configurable:true});Object.defineProperty(navigator,'appVersion',{get:()=>${appVersion},configurable:true});Object.defineProperty(navigator,'vendor',{get:()=>'Google Inc.',configurable:true});}catch(e){}`;
|
|
1449
|
+
}
|
|
1450
|
+
var uaCommand = registerCommand({
|
|
1451
|
+
name: "ua",
|
|
1452
|
+
description: "Get or override the session User-Agent (anti-bot UA detection countermeasure)",
|
|
1453
|
+
scope: "page",
|
|
1454
|
+
parameters: z10.object({
|
|
1455
|
+
value: z10.string().optional().describe('\u65B0\u7684 User-Agent\uFF1B\u7559\u7A7A\u5219\u8FD4\u56DE\u5F53\u524D UA\uFF1B"reset" \u6062\u590D\u5E38\u89C1 Chrome UA')
|
|
1456
|
+
}),
|
|
1457
|
+
handler: async (p, ctx) => {
|
|
1458
|
+
if (!p.value) {
|
|
1459
|
+
const current = await ctx.page.evaluate("navigator.userAgent");
|
|
1460
|
+
return ok10({ userAgent: current });
|
|
1461
|
+
}
|
|
1462
|
+
const ua = p.value.toLowerCase() === "reset" ? "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36" : p.value;
|
|
1463
|
+
const script = uaHookScript(ua);
|
|
1464
|
+
await ctx.page.addInitScript(script);
|
|
1465
|
+
await ctx.page.evaluate(`(function(){${script}})()`);
|
|
1466
|
+
return ok10({ userAgent: ua, mode: "init-script" });
|
|
1467
|
+
}
|
|
1468
|
+
});
|
|
1469
|
+
|
|
1003
1470
|
// src/commands/storage.ts
|
|
1004
|
-
import { z as
|
|
1005
|
-
import { ok as
|
|
1471
|
+
import { z as z11 } from "zod";
|
|
1472
|
+
import { ok as ok11, fail as fail4 } from "@dyyz1993/xcli-core";
|
|
1006
1473
|
var getCookiesCommand = registerCommand({
|
|
1007
1474
|
name: "get-cookies",
|
|
1008
1475
|
description: "Get all cookies for the current page",
|
|
1009
1476
|
scope: "page",
|
|
1010
|
-
result:
|
|
1011
|
-
cookies:
|
|
1477
|
+
result: z11.object({
|
|
1478
|
+
cookies: z11.array(z11.record(z11.unknown()))
|
|
1012
1479
|
}),
|
|
1013
1480
|
handler: async (_p, ctx) => {
|
|
1014
1481
|
const cookies = await ctx.browserContext.cookies();
|
|
1015
|
-
return
|
|
1482
|
+
return ok11({ cookies });
|
|
1016
1483
|
}
|
|
1017
1484
|
});
|
|
1018
1485
|
var setCookieCommand = registerCommand({
|
|
1019
1486
|
name: "set-cookie",
|
|
1020
1487
|
description: "Set a cookie",
|
|
1021
1488
|
scope: "page",
|
|
1022
|
-
parameters:
|
|
1023
|
-
name:
|
|
1024
|
-
value:
|
|
1025
|
-
domain:
|
|
1026
|
-
path:
|
|
1027
|
-
url:
|
|
1028
|
-
expires:
|
|
1029
|
-
httpOnly:
|
|
1030
|
-
secure:
|
|
1031
|
-
sameSite:
|
|
1489
|
+
parameters: z11.object({
|
|
1490
|
+
name: z11.coerce.string(),
|
|
1491
|
+
value: z11.coerce.string(),
|
|
1492
|
+
domain: z11.coerce.string().optional(),
|
|
1493
|
+
path: z11.coerce.string().optional(),
|
|
1494
|
+
url: z11.string().optional().describe("Cookie URL (alternative to domain)"),
|
|
1495
|
+
expires: z11.number().optional(),
|
|
1496
|
+
httpOnly: z11.boolean().optional(),
|
|
1497
|
+
secure: z11.boolean().optional(),
|
|
1498
|
+
sameSite: z11.enum(["Strict", "Lax", "None"]).optional()
|
|
1032
1499
|
}),
|
|
1033
|
-
result:
|
|
1500
|
+
result: z11.object({ name: z11.string() }),
|
|
1034
1501
|
handler: async (p, ctx) => {
|
|
1035
1502
|
const cookie = { ...p };
|
|
1036
1503
|
if (!cookie.domain && !cookie.url) {
|
|
@@ -1045,38 +1512,38 @@ var setCookieCommand = registerCommand({
|
|
|
1045
1512
|
}
|
|
1046
1513
|
}
|
|
1047
1514
|
if (!cookie.domain && !cookie.url) {
|
|
1048
|
-
return
|
|
1515
|
+
return fail4("set-cookie requires --domain or --url, or a non-blank page URL to infer from");
|
|
1049
1516
|
}
|
|
1050
1517
|
await ctx.browserContext.addCookies([cookie]);
|
|
1051
|
-
return
|
|
1518
|
+
return ok11({ name: p.name });
|
|
1052
1519
|
}
|
|
1053
1520
|
});
|
|
1054
1521
|
var clearCookiesCommand = registerCommand({
|
|
1055
1522
|
name: "clear-cookies",
|
|
1056
1523
|
description: "Clear all cookies",
|
|
1057
1524
|
scope: "page",
|
|
1058
|
-
result:
|
|
1525
|
+
result: z11.object({ cleared: z11.boolean() }),
|
|
1059
1526
|
handler: async (_p, ctx) => {
|
|
1060
1527
|
await ctx.browserContext.clearCookies();
|
|
1061
|
-
return
|
|
1528
|
+
return ok11({ cleared: true });
|
|
1062
1529
|
}
|
|
1063
1530
|
});
|
|
1064
1531
|
var getLocalStorageCommand = registerCommand({
|
|
1065
1532
|
name: "get-local-storage",
|
|
1066
1533
|
description: "Get localStorage entries",
|
|
1067
1534
|
scope: "page",
|
|
1068
|
-
parameters:
|
|
1069
|
-
key:
|
|
1535
|
+
parameters: z11.object({
|
|
1536
|
+
key: z11.string().optional()
|
|
1070
1537
|
}),
|
|
1071
|
-
result:
|
|
1072
|
-
|
|
1073
|
-
|
|
1538
|
+
result: z11.union([
|
|
1539
|
+
z11.object({ key: z11.string(), value: z11.string().nullable() }),
|
|
1540
|
+
z11.object({ data: z11.record(z11.string()) })
|
|
1074
1541
|
]),
|
|
1075
1542
|
handler: async (p, ctx) => {
|
|
1076
1543
|
try {
|
|
1077
1544
|
if (p.key) {
|
|
1078
1545
|
const value = await ctx.page.evaluate((k) => localStorage.getItem(k), p.key);
|
|
1079
|
-
return
|
|
1546
|
+
return ok11({ key: p.key, value });
|
|
1080
1547
|
}
|
|
1081
1548
|
const data = await ctx.page.evaluate(() => {
|
|
1082
1549
|
const entries = {};
|
|
@@ -1086,9 +1553,9 @@ var getLocalStorageCommand = registerCommand({
|
|
|
1086
1553
|
}
|
|
1087
1554
|
return entries;
|
|
1088
1555
|
});
|
|
1089
|
-
return
|
|
1556
|
+
return ok11({ data });
|
|
1090
1557
|
} catch (e) {
|
|
1091
|
-
return
|
|
1558
|
+
return fail4(`localStorage not accessible on this page: ${e instanceof Error ? e.message : String(e)}`);
|
|
1092
1559
|
}
|
|
1093
1560
|
}
|
|
1094
1561
|
});
|
|
@@ -1096,11 +1563,11 @@ var setLocalStorageCommand = registerCommand({
|
|
|
1096
1563
|
name: "set-local-storage",
|
|
1097
1564
|
description: "Set a localStorage entry",
|
|
1098
1565
|
scope: "page",
|
|
1099
|
-
parameters:
|
|
1100
|
-
key:
|
|
1101
|
-
value:
|
|
1566
|
+
parameters: z11.object({
|
|
1567
|
+
key: z11.string(),
|
|
1568
|
+
value: z11.string()
|
|
1102
1569
|
}),
|
|
1103
|
-
result:
|
|
1570
|
+
result: z11.object({ key: z11.string() }),
|
|
1104
1571
|
handler: async (p, ctx) => {
|
|
1105
1572
|
try {
|
|
1106
1573
|
await ctx.page.evaluate(
|
|
@@ -1109,9 +1576,9 @@ var setLocalStorageCommand = registerCommand({
|
|
|
1109
1576
|
},
|
|
1110
1577
|
{ key: p.key, value: p.value }
|
|
1111
1578
|
);
|
|
1112
|
-
return
|
|
1579
|
+
return ok11({ key: p.key });
|
|
1113
1580
|
} catch (e) {
|
|
1114
|
-
return
|
|
1581
|
+
return fail4(`localStorage not accessible on this page: ${e instanceof Error ? e.message : String(e)}`);
|
|
1115
1582
|
}
|
|
1116
1583
|
}
|
|
1117
1584
|
});
|
|
@@ -1119,26 +1586,24 @@ var clearLocalStorageCommand = registerCommand({
|
|
|
1119
1586
|
name: "clear-local-storage",
|
|
1120
1587
|
description: "Clear all localStorage entries",
|
|
1121
1588
|
scope: "page",
|
|
1122
|
-
result:
|
|
1589
|
+
result: z11.object({ cleared: z11.boolean() }),
|
|
1123
1590
|
handler: async (_p, ctx) => {
|
|
1124
1591
|
try {
|
|
1125
1592
|
await ctx.page.evaluate(() => localStorage.clear());
|
|
1126
|
-
return
|
|
1593
|
+
return ok11({ cleared: true });
|
|
1127
1594
|
} catch (e) {
|
|
1128
|
-
return
|
|
1595
|
+
return fail4(`localStorage not accessible on this page: ${e instanceof Error ? e.message : String(e)}`);
|
|
1129
1596
|
}
|
|
1130
1597
|
}
|
|
1131
1598
|
});
|
|
1132
1599
|
|
|
1133
1600
|
// src/commands/screenshot.ts
|
|
1134
|
-
import { z as
|
|
1135
|
-
import { ok as
|
|
1601
|
+
import { z as z12 } from "zod";
|
|
1602
|
+
import { ok as ok12, fail as fail5 } from "@dyyz1993/xcli-core";
|
|
1136
1603
|
import { writeFileSync, mkdirSync } from "fs";
|
|
1137
|
-
import { dirname, join as
|
|
1138
|
-
import { homedir as homedir2 } from "os";
|
|
1139
|
-
var SCREENSHOTS_DIR = join2(homedir2(), ".xbrowser", "screenshots");
|
|
1604
|
+
import { dirname, join as join4 } from "path";
|
|
1140
1605
|
function ensureScreenshotsDir() {
|
|
1141
|
-
mkdirSync(
|
|
1606
|
+
mkdirSync(resolveScreenshotsDir(), { recursive: true });
|
|
1142
1607
|
}
|
|
1143
1608
|
function ensureParentDir(filePath) {
|
|
1144
1609
|
const dir = dirname(filePath);
|
|
@@ -1154,30 +1619,30 @@ function generateScreenshotPath(format) {
|
|
|
1154
1619
|
const timestamp = Date.now();
|
|
1155
1620
|
const random = Math.random().toString(36).slice(2, 8);
|
|
1156
1621
|
const ext = format === "jpeg" ? "jpg" : "png";
|
|
1157
|
-
return
|
|
1622
|
+
return join4(resolveScreenshotsDir(), `screenshot-${timestamp}-${random}.${ext}`);
|
|
1158
1623
|
}
|
|
1159
1624
|
var screenshotCommand = registerCommand({
|
|
1160
1625
|
name: "screenshot",
|
|
1161
|
-
description: "Take a screenshot of the page or element",
|
|
1626
|
+
description: "Take a screenshot of the page or element (auto-saved to the OS temp dir unless --output is given; persistent override: xbrowser config set screenshots.dir <path>)",
|
|
1162
1627
|
scope: "page",
|
|
1163
1628
|
selectorParams: ["selector"],
|
|
1164
|
-
parameters:
|
|
1165
|
-
selector:
|
|
1166
|
-
type:
|
|
1167
|
-
fullPage:
|
|
1168
|
-
output:
|
|
1169
|
-
base64:
|
|
1629
|
+
parameters: z12.object({
|
|
1630
|
+
selector: z12.string().optional(),
|
|
1631
|
+
type: z12.enum(["png", "jpeg"]).optional(),
|
|
1632
|
+
fullPage: z12.boolean().optional(),
|
|
1633
|
+
output: z12.string().optional(),
|
|
1634
|
+
base64: z12.boolean().optional().describe("Return base64 data instead of file path")
|
|
1170
1635
|
}),
|
|
1171
|
-
result:
|
|
1172
|
-
|
|
1173
|
-
data:
|
|
1174
|
-
format:
|
|
1175
|
-
size:
|
|
1636
|
+
result: z12.union([
|
|
1637
|
+
z12.object({
|
|
1638
|
+
data: z12.string(),
|
|
1639
|
+
format: z12.string(),
|
|
1640
|
+
size: z12.number()
|
|
1176
1641
|
}),
|
|
1177
|
-
|
|
1178
|
-
output:
|
|
1179
|
-
format:
|
|
1180
|
-
size:
|
|
1642
|
+
z12.object({
|
|
1643
|
+
output: z12.string(),
|
|
1644
|
+
format: z12.string(),
|
|
1645
|
+
size: z12.number()
|
|
1181
1646
|
})
|
|
1182
1647
|
]),
|
|
1183
1648
|
handler: async (p, ctx) => {
|
|
@@ -1195,21 +1660,21 @@ var screenshotCommand = registerCommand({
|
|
|
1195
1660
|
if (p.output) {
|
|
1196
1661
|
const dirErr = ensureParentDir(p.output);
|
|
1197
1662
|
if (dirErr) {
|
|
1198
|
-
return
|
|
1663
|
+
return fail5(`Cannot create directory for --output "${p.output}": ${dirErr}`);
|
|
1199
1664
|
}
|
|
1200
1665
|
try {
|
|
1201
1666
|
writeFileSync(p.output, buffer, "binary");
|
|
1202
1667
|
} catch (err) {
|
|
1203
|
-
return
|
|
1668
|
+
return fail5(`Failed to write screenshot to "${p.output}": ${err instanceof Error ? err.message : String(err)}`);
|
|
1204
1669
|
}
|
|
1205
|
-
return
|
|
1670
|
+
return ok12({
|
|
1206
1671
|
output: p.output,
|
|
1207
1672
|
format,
|
|
1208
1673
|
size: buffer.length
|
|
1209
1674
|
});
|
|
1210
1675
|
}
|
|
1211
1676
|
if (p.base64) {
|
|
1212
|
-
return
|
|
1677
|
+
return ok12({
|
|
1213
1678
|
data: buffer.toString("base64"),
|
|
1214
1679
|
format,
|
|
1215
1680
|
size: buffer.length
|
|
@@ -1218,7 +1683,7 @@ var screenshotCommand = registerCommand({
|
|
|
1218
1683
|
ensureScreenshotsDir();
|
|
1219
1684
|
const screenshotPath = generateScreenshotPath(format);
|
|
1220
1685
|
writeFileSync(screenshotPath, buffer, "binary");
|
|
1221
|
-
return
|
|
1686
|
+
return ok12({
|
|
1222
1687
|
output: screenshotPath,
|
|
1223
1688
|
format,
|
|
1224
1689
|
size: buffer.length
|
|
@@ -1227,19 +1692,19 @@ var screenshotCommand = registerCommand({
|
|
|
1227
1692
|
});
|
|
1228
1693
|
|
|
1229
1694
|
// src/commands/structure.ts
|
|
1230
|
-
import { z as
|
|
1231
|
-
import { ok as
|
|
1695
|
+
import { z as z13 } from "zod";
|
|
1696
|
+
import { ok as ok13 } from "@dyyz1993/xcli-core";
|
|
1232
1697
|
var structureCommand = registerCommand({
|
|
1233
1698
|
name: "structure",
|
|
1234
1699
|
description: "Get the DOM structure of the page or an element",
|
|
1235
1700
|
scope: "page",
|
|
1236
1701
|
selectorParams: ["selector"],
|
|
1237
|
-
parameters:
|
|
1238
|
-
selector:
|
|
1239
|
-
depth:
|
|
1702
|
+
parameters: z13.object({
|
|
1703
|
+
selector: z13.string().optional(),
|
|
1704
|
+
depth: z13.number().optional()
|
|
1240
1705
|
}),
|
|
1241
|
-
result:
|
|
1242
|
-
structure:
|
|
1706
|
+
result: z13.object({
|
|
1707
|
+
structure: z13.record(z13.unknown())
|
|
1243
1708
|
}),
|
|
1244
1709
|
handler: async (p, ctx) => {
|
|
1245
1710
|
const structure = await ctx.page.evaluate(
|
|
@@ -1271,30 +1736,30 @@ var structureCommand = registerCommand({
|
|
|
1271
1736
|
},
|
|
1272
1737
|
{ sel: p.selector || "body", maxDepth: p.depth || 5 }
|
|
1273
1738
|
);
|
|
1274
|
-
return
|
|
1739
|
+
return ok13({ structure });
|
|
1275
1740
|
}
|
|
1276
1741
|
});
|
|
1277
1742
|
|
|
1278
1743
|
// src/commands/viewport.ts
|
|
1279
|
-
import { z as
|
|
1280
|
-
import { ok as
|
|
1744
|
+
import { z as z14 } from "zod";
|
|
1745
|
+
import { ok as ok14 } from "@dyyz1993/xcli-core";
|
|
1281
1746
|
var setViewportCommand = registerCommand({
|
|
1282
1747
|
name: "set-viewport",
|
|
1283
1748
|
description: "Set the viewport size and properties",
|
|
1284
1749
|
scope: "browser",
|
|
1285
|
-
parameters:
|
|
1286
|
-
width:
|
|
1287
|
-
height:
|
|
1288
|
-
deviceScaleFactor:
|
|
1289
|
-
isMobile:
|
|
1290
|
-
hasTouch:
|
|
1750
|
+
parameters: z14.object({
|
|
1751
|
+
width: z14.coerce.number(),
|
|
1752
|
+
height: z14.coerce.number(),
|
|
1753
|
+
deviceScaleFactor: z14.coerce.number().optional(),
|
|
1754
|
+
isMobile: z14.boolean().optional(),
|
|
1755
|
+
hasTouch: z14.boolean().optional()
|
|
1291
1756
|
}),
|
|
1292
|
-
result:
|
|
1293
|
-
width:
|
|
1294
|
-
height:
|
|
1295
|
-
deviceScaleFactor:
|
|
1296
|
-
isMobile:
|
|
1297
|
-
hasTouch:
|
|
1757
|
+
result: z14.object({
|
|
1758
|
+
width: z14.number(),
|
|
1759
|
+
height: z14.number(),
|
|
1760
|
+
deviceScaleFactor: z14.number().optional(),
|
|
1761
|
+
isMobile: z14.boolean().optional(),
|
|
1762
|
+
hasTouch: z14.boolean().optional()
|
|
1298
1763
|
}),
|
|
1299
1764
|
handler: async (p, ctx) => {
|
|
1300
1765
|
const viewport = ctx.page.viewportSize();
|
|
@@ -1307,7 +1772,7 @@ var setViewportCommand = registerCommand({
|
|
|
1307
1772
|
...p.isMobile !== void 0 && { isMobile: p.isMobile },
|
|
1308
1773
|
...p.hasTouch !== void 0 && { hasTouch: p.hasTouch }
|
|
1309
1774
|
});
|
|
1310
|
-
return
|
|
1775
|
+
return ok14({
|
|
1311
1776
|
width,
|
|
1312
1777
|
height,
|
|
1313
1778
|
...p.deviceScaleFactor !== void 0 && { deviceScaleFactor: p.deviceScaleFactor },
|
|
@@ -1318,17 +1783,17 @@ var setViewportCommand = registerCommand({
|
|
|
1318
1783
|
});
|
|
1319
1784
|
|
|
1320
1785
|
// src/commands/frame.ts
|
|
1321
|
-
import { z as
|
|
1322
|
-
import { ok as
|
|
1786
|
+
import { z as z15 } from "zod";
|
|
1787
|
+
import { ok as ok15, fail as fail6 } from "@dyyz1993/xcli-core";
|
|
1323
1788
|
var framesCommand = registerCommand({
|
|
1324
1789
|
name: "frames",
|
|
1325
1790
|
description: "List all frames in the current page",
|
|
1326
1791
|
scope: "page",
|
|
1327
|
-
result:
|
|
1328
|
-
frames:
|
|
1329
|
-
index:
|
|
1330
|
-
name:
|
|
1331
|
-
url:
|
|
1792
|
+
result: z15.object({
|
|
1793
|
+
frames: z15.array(z15.object({
|
|
1794
|
+
index: z15.number(),
|
|
1795
|
+
name: z15.string().nullable(),
|
|
1796
|
+
url: z15.string()
|
|
1332
1797
|
}))
|
|
1333
1798
|
}),
|
|
1334
1799
|
handler: async (_p, ctx) => {
|
|
@@ -1339,21 +1804,21 @@ var framesCommand = registerCommand({
|
|
|
1339
1804
|
name: frame.name(),
|
|
1340
1805
|
url: frame.url()
|
|
1341
1806
|
}));
|
|
1342
|
-
return
|
|
1807
|
+
return ok15({ frames: frameList });
|
|
1343
1808
|
}
|
|
1344
1809
|
});
|
|
1345
1810
|
var frameCommand = registerCommand({
|
|
1346
1811
|
name: "frame",
|
|
1347
1812
|
description: "Get frame info by index or name",
|
|
1348
1813
|
scope: "page",
|
|
1349
|
-
parameters:
|
|
1350
|
-
index:
|
|
1351
|
-
name:
|
|
1814
|
+
parameters: z15.object({
|
|
1815
|
+
index: z15.number().int().min(0).optional(),
|
|
1816
|
+
name: z15.string().optional()
|
|
1352
1817
|
}),
|
|
1353
|
-
result:
|
|
1354
|
-
name:
|
|
1355
|
-
url:
|
|
1356
|
-
error:
|
|
1818
|
+
result: z15.object({
|
|
1819
|
+
name: z15.string().nullable(),
|
|
1820
|
+
url: z15.string(),
|
|
1821
|
+
error: z15.string().optional()
|
|
1357
1822
|
}),
|
|
1358
1823
|
handler: async (p, ctx) => {
|
|
1359
1824
|
const discover = ctx.page.discoverFrames;
|
|
@@ -1364,12 +1829,12 @@ var frameCommand = registerCommand({
|
|
|
1364
1829
|
} else if (p.name !== void 0) {
|
|
1365
1830
|
targetFrame = rawFrames.find((f) => f.name() === p.name);
|
|
1366
1831
|
} else {
|
|
1367
|
-
return
|
|
1832
|
+
return fail6("Must provide index or name");
|
|
1368
1833
|
}
|
|
1369
1834
|
if (!targetFrame) {
|
|
1370
|
-
return
|
|
1835
|
+
return fail6("Frame not found");
|
|
1371
1836
|
}
|
|
1372
|
-
return
|
|
1837
|
+
return ok15({
|
|
1373
1838
|
name: targetFrame.name(),
|
|
1374
1839
|
url: targetFrame.url()
|
|
1375
1840
|
});
|
|
@@ -1377,27 +1842,27 @@ var frameCommand = registerCommand({
|
|
|
1377
1842
|
});
|
|
1378
1843
|
|
|
1379
1844
|
// src/commands/ui-debug.ts
|
|
1380
|
-
import { z as
|
|
1381
|
-
import { ok as
|
|
1845
|
+
import { z as z16 } from "zod";
|
|
1846
|
+
import { ok as ok16 } from "@dyyz1993/xcli-core";
|
|
1382
1847
|
var consoleCheckCommand = registerCommand({
|
|
1383
1848
|
name: "console",
|
|
1384
1849
|
description: "Collect and analyze browser console messages (errors, warnings, logs)",
|
|
1385
1850
|
scope: "page",
|
|
1386
|
-
parameters:
|
|
1387
|
-
url:
|
|
1388
|
-
duration:
|
|
1389
|
-
filter:
|
|
1390
|
-
includeStackTraces:
|
|
1851
|
+
parameters: z16.object({
|
|
1852
|
+
url: z16.string().optional().describe("URL to navigate first (optional, uses current page if omitted)"),
|
|
1853
|
+
duration: z16.number().optional().default(5e3).describe("How long to collect messages (ms)"),
|
|
1854
|
+
filter: z16.enum(["all", "error", "warning", "info", "log"]).optional().default("all"),
|
|
1855
|
+
includeStackTraces: z16.boolean().optional().default(true)
|
|
1391
1856
|
}),
|
|
1392
|
-
result:
|
|
1393
|
-
url:
|
|
1394
|
-
duration:
|
|
1395
|
-
total:
|
|
1396
|
-
errors:
|
|
1397
|
-
warnings:
|
|
1398
|
-
messages:
|
|
1399
|
-
summary:
|
|
1400
|
-
passed:
|
|
1857
|
+
result: z16.object({
|
|
1858
|
+
url: z16.string(),
|
|
1859
|
+
duration: z16.number(),
|
|
1860
|
+
total: z16.number(),
|
|
1861
|
+
errors: z16.number(),
|
|
1862
|
+
warnings: z16.number(),
|
|
1863
|
+
messages: z16.array(z16.record(z16.unknown())),
|
|
1864
|
+
summary: z16.string(),
|
|
1865
|
+
passed: z16.boolean()
|
|
1401
1866
|
}),
|
|
1402
1867
|
handler: async (p, ctx) => {
|
|
1403
1868
|
const { page } = ctx;
|
|
@@ -1479,7 +1944,7 @@ ${a.stack || ""}`;
|
|
|
1479
1944
|
const filtered = p.filter === "all" ? messages : messages.filter((m) => m.type === p.filter);
|
|
1480
1945
|
const errorCount = messages.filter((m) => m.type === "error").length;
|
|
1481
1946
|
const warnCount = messages.filter((m) => m.type === "warning").length;
|
|
1482
|
-
return
|
|
1947
|
+
return ok16({
|
|
1483
1948
|
url: page.url(),
|
|
1484
1949
|
duration: p.duration,
|
|
1485
1950
|
total: messages.length,
|
|
@@ -1495,23 +1960,23 @@ var networkCheckCommand = registerCommand({
|
|
|
1495
1960
|
name: "net-debug",
|
|
1496
1961
|
description: "Monitor and analyze network requests \u2014 capture failed requests, slow responses, status codes",
|
|
1497
1962
|
scope: "page",
|
|
1498
|
-
parameters:
|
|
1499
|
-
url:
|
|
1500
|
-
duration:
|
|
1501
|
-
filter:
|
|
1502
|
-
slowThreshold:
|
|
1963
|
+
parameters: z16.object({
|
|
1964
|
+
url: z16.string().optional().describe("URL to navigate first"),
|
|
1965
|
+
duration: z16.number().optional().default(5e3).describe("How long to monitor (ms)"),
|
|
1966
|
+
filter: z16.enum(["all", "failed", "slow", "error", "xhr", "fetch", "document", "stylesheet", "script", "image"]).optional().default("all"),
|
|
1967
|
+
slowThreshold: z16.number().optional().default(3e3).describe('Threshold for "slow" requests (ms)')
|
|
1503
1968
|
}),
|
|
1504
|
-
result:
|
|
1505
|
-
url:
|
|
1506
|
-
duration:
|
|
1507
|
-
totalRequests:
|
|
1508
|
-
failedRequests:
|
|
1509
|
-
slowRequests:
|
|
1510
|
-
errorRequests:
|
|
1511
|
-
totalSizeKB:
|
|
1512
|
-
requests:
|
|
1513
|
-
summary:
|
|
1514
|
-
passed:
|
|
1969
|
+
result: z16.object({
|
|
1970
|
+
url: z16.string(),
|
|
1971
|
+
duration: z16.number(),
|
|
1972
|
+
totalRequests: z16.number(),
|
|
1973
|
+
failedRequests: z16.number(),
|
|
1974
|
+
slowRequests: z16.number(),
|
|
1975
|
+
errorRequests: z16.number(),
|
|
1976
|
+
totalSizeKB: z16.number(),
|
|
1977
|
+
requests: z16.array(z16.record(z16.unknown())),
|
|
1978
|
+
summary: z16.string(),
|
|
1979
|
+
passed: z16.boolean()
|
|
1515
1980
|
}),
|
|
1516
1981
|
handler: async (p, ctx) => {
|
|
1517
1982
|
const { page } = ctx;
|
|
@@ -1593,7 +2058,7 @@ var networkCheckCommand = registerCommand({
|
|
|
1593
2058
|
const slowCount = requests.filter((r) => r.duration >= p.slowThreshold).length;
|
|
1594
2059
|
const errorCount = requests.filter((r) => r.error).length;
|
|
1595
2060
|
const totalSize = requests.reduce((sum, r) => sum + r.size, 0);
|
|
1596
|
-
return
|
|
2061
|
+
return ok16({
|
|
1597
2062
|
url: page.url(),
|
|
1598
2063
|
duration: p.duration,
|
|
1599
2064
|
totalRequests: requests.length,
|
|
@@ -1615,17 +2080,17 @@ var perfCheckCommand = registerCommand({
|
|
|
1615
2080
|
name: "perf",
|
|
1616
2081
|
description: "Audit page performance metrics \u2014 load time, FCP, LCP, CLS, TTFB, resource sizes",
|
|
1617
2082
|
scope: "page",
|
|
1618
|
-
parameters:
|
|
1619
|
-
url:
|
|
1620
|
-
iterations:
|
|
2083
|
+
parameters: z16.object({
|
|
2084
|
+
url: z16.string().optional().describe("URL to navigate (uses current page if omitted)"),
|
|
2085
|
+
iterations: z16.number().optional().default(1).describe("Number of iterations to average")
|
|
1621
2086
|
}),
|
|
1622
|
-
result:
|
|
1623
|
-
url:
|
|
1624
|
-
iterations:
|
|
1625
|
-
metrics:
|
|
1626
|
-
allIterations:
|
|
1627
|
-
passed:
|
|
1628
|
-
summary:
|
|
2087
|
+
result: z16.object({
|
|
2088
|
+
url: z16.string(),
|
|
2089
|
+
iterations: z16.number(),
|
|
2090
|
+
metrics: z16.record(z16.unknown()),
|
|
2091
|
+
allIterations: z16.array(z16.record(z16.unknown())).optional(),
|
|
2092
|
+
passed: z16.boolean(),
|
|
2093
|
+
summary: z16.string()
|
|
1629
2094
|
}),
|
|
1630
2095
|
handler: async (p, ctx) => {
|
|
1631
2096
|
const { page } = ctx;
|
|
@@ -1687,7 +2152,7 @@ var perfCheckCommand = registerCommand({
|
|
|
1687
2152
|
if (resourceStats) avg.resourceStats = resourceStats;
|
|
1688
2153
|
return avg;
|
|
1689
2154
|
})();
|
|
1690
|
-
return
|
|
2155
|
+
return ok16({
|
|
1691
2156
|
url: page.url(),
|
|
1692
2157
|
iterations: p.iterations,
|
|
1693
2158
|
metrics: avgMetrics,
|
|
@@ -1701,23 +2166,23 @@ var healthCheckCommand = registerCommand({
|
|
|
1701
2166
|
name: "health",
|
|
1702
2167
|
description: "Comprehensive page health check \u2014 broken links, missing images, console errors, SEO issues",
|
|
1703
2168
|
scope: "page",
|
|
1704
|
-
parameters:
|
|
1705
|
-
url:
|
|
1706
|
-
checkLinks:
|
|
1707
|
-
checkImages:
|
|
1708
|
-
checkMeta:
|
|
1709
|
-
maxLinks:
|
|
2169
|
+
parameters: z16.object({
|
|
2170
|
+
url: z16.string().optional().describe("URL to check"),
|
|
2171
|
+
checkLinks: z16.boolean().optional().default(true).describe("Check for broken links"),
|
|
2172
|
+
checkImages: z16.boolean().optional().default(true).describe("Check for missing/broken images"),
|
|
2173
|
+
checkMeta: z16.boolean().optional().default(true).describe("Check SEO meta tags"),
|
|
2174
|
+
maxLinks: z16.number().optional().default(50).describe("Max links to check")
|
|
1710
2175
|
}),
|
|
1711
|
-
result:
|
|
1712
|
-
url:
|
|
1713
|
-
title:
|
|
1714
|
-
passed:
|
|
1715
|
-
totalIssues:
|
|
1716
|
-
errors:
|
|
1717
|
-
warnings:
|
|
1718
|
-
info:
|
|
1719
|
-
issues:
|
|
1720
|
-
summary:
|
|
2176
|
+
result: z16.object({
|
|
2177
|
+
url: z16.string(),
|
|
2178
|
+
title: z16.string(),
|
|
2179
|
+
passed: z16.boolean(),
|
|
2180
|
+
totalIssues: z16.number(),
|
|
2181
|
+
errors: z16.number(),
|
|
2182
|
+
warnings: z16.number(),
|
|
2183
|
+
info: z16.number(),
|
|
2184
|
+
issues: z16.array(z16.record(z16.unknown())),
|
|
2185
|
+
summary: z16.string()
|
|
1721
2186
|
}),
|
|
1722
2187
|
handler: async (p, ctx) => {
|
|
1723
2188
|
const { page } = ctx;
|
|
@@ -1804,7 +2269,7 @@ var healthCheckCommand = registerCommand({
|
|
|
1804
2269
|
}, { checkLinks: p.checkLinks, checkImages: p.checkImages, checkMeta: p.checkMeta, maxLinks: p.maxLinks });
|
|
1805
2270
|
const errors = result.issues.filter((i) => i.severity === "error").length;
|
|
1806
2271
|
const warnings = result.issues.filter((i) => i.severity === "warning").length;
|
|
1807
|
-
return
|
|
2272
|
+
return ok16({
|
|
1808
2273
|
url: result.url,
|
|
1809
2274
|
title: result.title,
|
|
1810
2275
|
passed: errors === 0,
|
|
@@ -1819,68 +2284,66 @@ var healthCheckCommand = registerCommand({
|
|
|
1819
2284
|
});
|
|
1820
2285
|
|
|
1821
2286
|
// src/commands/actions.ts
|
|
1822
|
-
import { z as
|
|
1823
|
-
import { ok as
|
|
2287
|
+
import { z as z17 } from "zod";
|
|
2288
|
+
import { ok as ok17 } from "@dyyz1993/xcli-core";
|
|
1824
2289
|
import { writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
1825
|
-
import { join as
|
|
1826
|
-
import { homedir as homedir3 } from "os";
|
|
1827
|
-
var SCREENSHOTS_DIR2 = join3(homedir3(), ".xbrowser", "screenshots");
|
|
2290
|
+
import { join as join5 } from "path";
|
|
1828
2291
|
function ensureScreenshotsDir2() {
|
|
1829
|
-
mkdirSync2(
|
|
2292
|
+
mkdirSync2(resolveScreenshotsDir(), { recursive: true });
|
|
1830
2293
|
}
|
|
1831
2294
|
function generateScreenshotPath2(format) {
|
|
1832
2295
|
const timestamp = Date.now();
|
|
1833
2296
|
const random = Math.random().toString(36).slice(2, 8);
|
|
1834
2297
|
const ext = format === "jpeg" ? "jpg" : "png";
|
|
1835
|
-
return
|
|
2298
|
+
return join5(resolveScreenshotsDir(), `screenshot-${timestamp}-${random}.${ext}`);
|
|
1836
2299
|
}
|
|
1837
|
-
var waitActionSchema =
|
|
1838
|
-
type:
|
|
1839
|
-
milliseconds:
|
|
1840
|
-
selector:
|
|
2300
|
+
var waitActionSchema = z17.object({
|
|
2301
|
+
type: z17.literal("wait"),
|
|
2302
|
+
milliseconds: z17.number().positive().optional(),
|
|
2303
|
+
selector: z17.string().optional()
|
|
1841
2304
|
}).refine(
|
|
1842
2305
|
(d) => (d.milliseconds !== void 0 || d.selector !== void 0) && !(d.milliseconds !== void 0 && d.selector !== void 0),
|
|
1843
2306
|
{ message: "Either 'milliseconds' or 'selector' must be provided, but not both." }
|
|
1844
2307
|
);
|
|
1845
|
-
var clickActionSchema =
|
|
1846
|
-
type:
|
|
1847
|
-
selector:
|
|
1848
|
-
all:
|
|
2308
|
+
var clickActionSchema = z17.object({
|
|
2309
|
+
type: z17.literal("click"),
|
|
2310
|
+
selector: z17.string(),
|
|
2311
|
+
all: z17.boolean().optional()
|
|
1849
2312
|
});
|
|
1850
|
-
var screenshotActionSchema =
|
|
1851
|
-
type:
|
|
1852
|
-
fullPage:
|
|
1853
|
-
quality:
|
|
1854
|
-
viewport:
|
|
1855
|
-
base64:
|
|
2313
|
+
var screenshotActionSchema = z17.object({
|
|
2314
|
+
type: z17.literal("screenshot"),
|
|
2315
|
+
fullPage: z17.boolean().optional(),
|
|
2316
|
+
quality: z17.number().min(1).max(100).optional(),
|
|
2317
|
+
viewport: z17.object({ width: z17.number().int().positive(), height: z17.number().int().positive() }).optional(),
|
|
2318
|
+
base64: z17.boolean().optional().describe("Return base64 data instead of file path")
|
|
1856
2319
|
});
|
|
1857
|
-
var writeActionSchema =
|
|
1858
|
-
type:
|
|
1859
|
-
text:
|
|
2320
|
+
var writeActionSchema = z17.object({
|
|
2321
|
+
type: z17.literal("write"),
|
|
2322
|
+
text: z17.string()
|
|
1860
2323
|
});
|
|
1861
|
-
var pressActionSchema =
|
|
1862
|
-
type:
|
|
1863
|
-
key:
|
|
2324
|
+
var pressActionSchema = z17.object({
|
|
2325
|
+
type: z17.literal("press"),
|
|
2326
|
+
key: z17.string()
|
|
1864
2327
|
});
|
|
1865
|
-
var scrollActionSchema =
|
|
1866
|
-
type:
|
|
1867
|
-
direction:
|
|
1868
|
-
selector:
|
|
2328
|
+
var scrollActionSchema = z17.object({
|
|
2329
|
+
type: z17.literal("scroll"),
|
|
2330
|
+
direction: z17.enum(["up", "down"]).optional(),
|
|
2331
|
+
selector: z17.string().optional()
|
|
1869
2332
|
});
|
|
1870
|
-
var scrapeActionSchema =
|
|
1871
|
-
type:
|
|
2333
|
+
var scrapeActionSchema = z17.object({
|
|
2334
|
+
type: z17.literal("scrape")
|
|
1872
2335
|
});
|
|
1873
|
-
var executeJavascriptActionSchema =
|
|
1874
|
-
type:
|
|
1875
|
-
script:
|
|
2336
|
+
var executeJavascriptActionSchema = z17.object({
|
|
2337
|
+
type: z17.literal("executeJavascript"),
|
|
2338
|
+
script: z17.string()
|
|
1876
2339
|
});
|
|
1877
|
-
var pdfActionSchema =
|
|
1878
|
-
type:
|
|
1879
|
-
landscape:
|
|
1880
|
-
scale:
|
|
1881
|
-
format:
|
|
2340
|
+
var pdfActionSchema = z17.object({
|
|
2341
|
+
type: z17.literal("pdf"),
|
|
2342
|
+
landscape: z17.boolean().optional(),
|
|
2343
|
+
scale: z17.number().optional(),
|
|
2344
|
+
format: z17.enum(["A0", "A1", "A2", "A3", "A4", "A5", "A6", "Letter", "Legal", "Tabloid", "Ledger"]).optional()
|
|
1882
2345
|
});
|
|
1883
|
-
var actionSchema =
|
|
2346
|
+
var actionSchema = z17.union([
|
|
1884
2347
|
waitActionSchema,
|
|
1885
2348
|
clickActionSchema,
|
|
1886
2349
|
screenshotActionSchema,
|
|
@@ -1971,11 +2434,11 @@ var actionsCommand = registerCommand({
|
|
|
1971
2434
|
name: "actions",
|
|
1972
2435
|
description: "Execute a sequence of actions (wait, click, scroll, screenshot, fill, etc.)",
|
|
1973
2436
|
scope: "page",
|
|
1974
|
-
parameters:
|
|
1975
|
-
url:
|
|
1976
|
-
actions:
|
|
1977
|
-
output:
|
|
1978
|
-
timeout:
|
|
2437
|
+
parameters: z17.object({
|
|
2438
|
+
url: z17.string().describe("Starting URL"),
|
|
2439
|
+
actions: z17.array(actionSchema).max(MAX_ACTIONS).describe("Array of actions (max 50)"),
|
|
2440
|
+
output: z17.enum(["text", "json"]).default("json").describe("Output format: text or json"),
|
|
2441
|
+
timeout: z17.number().default(60).describe("Overall timeout in seconds (default: 60)")
|
|
1979
2442
|
}),
|
|
1980
2443
|
handler: async (p, ctx) => {
|
|
1981
2444
|
await ctx.page.goto(p.url, { waitUntil: "domcontentloaded" });
|
|
@@ -1995,14 +2458,14 @@ var actionsCommand = registerCommand({
|
|
|
1995
2458
|
const finalUrl = ctx.page.url();
|
|
1996
2459
|
const timedOut = results.length < p.actions.length;
|
|
1997
2460
|
if (p.output === "text") {
|
|
1998
|
-
return
|
|
2461
|
+
return ok17({
|
|
1999
2462
|
title,
|
|
2000
2463
|
url: finalUrl,
|
|
2001
2464
|
actions: results.map((r) => JSON.stringify(r)).join("\n"),
|
|
2002
2465
|
...timedOut ? { warning: `Timed out after ${p.timeout}s, completed ${results.length}/${p.actions.length} actions` } : {}
|
|
2003
2466
|
});
|
|
2004
2467
|
}
|
|
2005
|
-
return
|
|
2468
|
+
return ok17({
|
|
2006
2469
|
title,
|
|
2007
2470
|
url: finalUrl,
|
|
2008
2471
|
results,
|
|
@@ -2012,8 +2475,8 @@ var actionsCommand = registerCommand({
|
|
|
2012
2475
|
});
|
|
2013
2476
|
|
|
2014
2477
|
// src/commands/scrape.ts
|
|
2015
|
-
import { z as
|
|
2016
|
-
import { ok as
|
|
2478
|
+
import { z as z18 } from "zod";
|
|
2479
|
+
import { ok as ok18, fail as fail7 } from "@dyyz1993/xcli-core";
|
|
2017
2480
|
|
|
2018
2481
|
// src/lib/html-to-markdown.ts
|
|
2019
2482
|
import * as cheerio from "cheerio";
|
|
@@ -2398,15 +2861,15 @@ var scrapeCommand = registerCommand({
|
|
|
2398
2861
|
description: "Scrape a page and convert to Markdown (with JS rendering)",
|
|
2399
2862
|
scope: "project",
|
|
2400
2863
|
selectorParams: ["selector"],
|
|
2401
|
-
parameters:
|
|
2402
|
-
url:
|
|
2403
|
-
selector:
|
|
2404
|
-
timeout:
|
|
2405
|
-
format:
|
|
2406
|
-
onlyMainContent:
|
|
2407
|
-
retries:
|
|
2408
|
-
waitAfterLoad:
|
|
2409
|
-
mode:
|
|
2864
|
+
parameters: z18.object({
|
|
2865
|
+
url: z18.string().optional(),
|
|
2866
|
+
selector: z18.string().optional(),
|
|
2867
|
+
timeout: z18.number().default(3e4),
|
|
2868
|
+
format: z18.enum(["markdown", "html", "text"]).default("markdown"),
|
|
2869
|
+
onlyMainContent: z18.boolean().default(true),
|
|
2870
|
+
retries: z18.number().int().min(0).max(5).optional().default(2).describe("\u91CD\u8BD5\u6B21\u6570\uFF08\u9ED8\u8BA4 2\uFF09"),
|
|
2871
|
+
waitAfterLoad: z18.number().int().optional().default(0).describe("\u9875\u9762\u52A0\u8F7D\u540E\u989D\u5916\u7B49\u5F85\u6BEB\u79D2"),
|
|
2872
|
+
mode: z18.enum(["raw", "clean", "compact", "smart"]).default("raw").describe("\u8F93\u51FA\u6A21\u5F0F\uFF1Araw\uFF08\u9ED8\u8BA4\uFF09/ clean\uFF08\u7ED3\u6784\u5316\uFF09/ compact\uFF08\u7CBE\u7B80\uFF09/ smart\uFF08\u7ED3\u6784\u5316\uFF0C\u7559\u7ED9 agent \u589E\u5F3A\uFF09")
|
|
2410
2873
|
}),
|
|
2411
2874
|
handler: async (p, ctx) => {
|
|
2412
2875
|
const { context, page } = await createEphemeralContext(resolveLaunchOpts(ctx));
|
|
@@ -2414,7 +2877,7 @@ var scrapeCommand = registerCommand({
|
|
|
2414
2877
|
try {
|
|
2415
2878
|
const targetUrl = p.url || page.url();
|
|
2416
2879
|
if (!targetUrl || targetUrl === "about:blank") {
|
|
2417
|
-
return
|
|
2880
|
+
return fail7("URL is required. Provide a URL or connect to a browser (--cdp) with a non-blank page.");
|
|
2418
2881
|
}
|
|
2419
2882
|
let lastError;
|
|
2420
2883
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
@@ -2484,7 +2947,7 @@ var scrapeCommand = registerCommand({
|
|
|
2484
2947
|
} catch {
|
|
2485
2948
|
}
|
|
2486
2949
|
if (p.mode === "smart") {
|
|
2487
|
-
return
|
|
2950
|
+
return ok18(structured);
|
|
2488
2951
|
}
|
|
2489
2952
|
if (p.mode === "compact") {
|
|
2490
2953
|
const compactData = structured.tables.length > 0 ? structured.tables.map((t) => ({
|
|
@@ -2497,9 +2960,9 @@ var scrapeCommand = registerCommand({
|
|
|
2497
2960
|
return compact;
|
|
2498
2961
|
})
|
|
2499
2962
|
})) : structured.mainText?.substring(0, 500);
|
|
2500
|
-
return
|
|
2963
|
+
return ok18({ url: structured.url, title: structured.title, data: compactData });
|
|
2501
2964
|
}
|
|
2502
|
-
return
|
|
2965
|
+
return ok18(structured);
|
|
2503
2966
|
}
|
|
2504
2967
|
let content;
|
|
2505
2968
|
switch (p.format) {
|
|
@@ -2558,7 +3021,7 @@ var scrapeCommand = registerCommand({
|
|
|
2558
3021
|
content = await page.innerText("body");
|
|
2559
3022
|
break;
|
|
2560
3023
|
}
|
|
2561
|
-
return
|
|
3024
|
+
return ok18({ content, title, url: finalUrl });
|
|
2562
3025
|
} catch (err) {
|
|
2563
3026
|
lastError = err instanceof Error ? err : new Error(String(err));
|
|
2564
3027
|
if (attempt < maxAttempts) {
|
|
@@ -2568,20 +3031,20 @@ var scrapeCommand = registerCommand({
|
|
|
2568
3031
|
}
|
|
2569
3032
|
}
|
|
2570
3033
|
}
|
|
2571
|
-
return
|
|
3034
|
+
return fail7(`Scrape failed after ${maxAttempts} attempt(s): ${lastError?.message ?? "unknown error"}`);
|
|
2572
3035
|
} finally {
|
|
2573
3036
|
await closeEphemeralContext(context);
|
|
2574
3037
|
}
|
|
2575
3038
|
},
|
|
2576
|
-
result:
|
|
2577
|
-
url:
|
|
2578
|
-
title:
|
|
3039
|
+
result: z18.object({
|
|
3040
|
+
url: z18.string(),
|
|
3041
|
+
title: z18.string()
|
|
2579
3042
|
}).passthrough()
|
|
2580
3043
|
});
|
|
2581
3044
|
|
|
2582
3045
|
// src/commands/map.ts
|
|
2583
|
-
import { z as
|
|
2584
|
-
import { ok as
|
|
3046
|
+
import { z as z19 } from "zod";
|
|
3047
|
+
import { ok as ok19, fail as fail8 } from "@dyyz1993/xcli-core";
|
|
2585
3048
|
|
|
2586
3049
|
// src/utils/url.ts
|
|
2587
3050
|
var SKIP_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
@@ -2816,21 +3279,21 @@ var mapCommand = registerCommand({
|
|
|
2816
3279
|
name: "map",
|
|
2817
3280
|
description: "Discover all URLs on a website via sitemap and page link extraction",
|
|
2818
3281
|
scope: "project",
|
|
2819
|
-
parameters:
|
|
2820
|
-
url:
|
|
2821
|
-
search:
|
|
2822
|
-
sitemap:
|
|
2823
|
-
includeSubdomains:
|
|
2824
|
-
allowExternalLinks:
|
|
2825
|
-
limit:
|
|
2826
|
-
verbose:
|
|
3282
|
+
parameters: z19.object({
|
|
3283
|
+
url: z19.string().optional(),
|
|
3284
|
+
search: z19.string().optional(),
|
|
3285
|
+
sitemap: z19.enum(["include", "only"]).optional(),
|
|
3286
|
+
includeSubdomains: z19.boolean().optional(),
|
|
3287
|
+
allowExternalLinks: z19.boolean().optional().describe("Include links to external domains"),
|
|
3288
|
+
limit: z19.number().optional(),
|
|
3289
|
+
verbose: z19.boolean().default(false).describe("Show progress feedback")
|
|
2827
3290
|
}),
|
|
2828
3291
|
handler: async (p, ctx) => {
|
|
2829
3292
|
const { context, page } = await createEphemeralContext(resolveLaunchOpts(ctx));
|
|
2830
3293
|
try {
|
|
2831
3294
|
const targetUrl = p.url || page.url();
|
|
2832
3295
|
if (!targetUrl || targetUrl === "about:blank") {
|
|
2833
|
-
return
|
|
3296
|
+
return fail8("URL is required. Provide a URL or connect to a browser (--cdp) with a non-blank page.");
|
|
2834
3297
|
}
|
|
2835
3298
|
const links = await discoverUrls(page, targetUrl, {
|
|
2836
3299
|
sitemap: p.sitemap,
|
|
@@ -2841,7 +3304,7 @@ var mapCommand = registerCommand({
|
|
|
2841
3304
|
verbose: p.verbose
|
|
2842
3305
|
});
|
|
2843
3306
|
const linkObjects = links.map((url) => ({ url }));
|
|
2844
|
-
return
|
|
3307
|
+
return ok19({
|
|
2845
3308
|
links: linkObjects,
|
|
2846
3309
|
success: true
|
|
2847
3310
|
});
|
|
@@ -2849,15 +3312,15 @@ var mapCommand = registerCommand({
|
|
|
2849
3312
|
await closeEphemeralContext(context);
|
|
2850
3313
|
}
|
|
2851
3314
|
},
|
|
2852
|
-
result:
|
|
2853
|
-
links:
|
|
2854
|
-
success:
|
|
3315
|
+
result: z19.object({
|
|
3316
|
+
links: z19.array(z19.object({ url: z19.string() })),
|
|
3317
|
+
success: z19.boolean()
|
|
2855
3318
|
})
|
|
2856
3319
|
});
|
|
2857
3320
|
|
|
2858
3321
|
// src/commands/crawl.ts
|
|
2859
|
-
import { z as
|
|
2860
|
-
import { ok as
|
|
3322
|
+
import { z as z20 } from "zod";
|
|
3323
|
+
import { ok as ok20 } from "@dyyz1993/xcli-core";
|
|
2861
3324
|
function stripHashAnchorQuery(url) {
|
|
2862
3325
|
try {
|
|
2863
3326
|
const parsed = new URL(url);
|
|
@@ -3034,21 +3497,21 @@ var crawlCommand = registerCommand({
|
|
|
3034
3497
|
name: "crawl",
|
|
3035
3498
|
description: "Crawl a website and extract content from all pages",
|
|
3036
3499
|
scope: "project",
|
|
3037
|
-
parameters:
|
|
3038
|
-
url:
|
|
3039
|
-
limit:
|
|
3040
|
-
maxDepth:
|
|
3041
|
-
includePaths:
|
|
3042
|
-
excludePaths:
|
|
3043
|
-
allowSubdomains:
|
|
3044
|
-
allowExternalLinks:
|
|
3045
|
-
allowBackwardCrawling:
|
|
3046
|
-
enableSpa:
|
|
3047
|
-
format:
|
|
3048
|
-
onlyMainContent:
|
|
3049
|
-
concurrency:
|
|
3050
|
-
retries:
|
|
3051
|
-
verbose:
|
|
3500
|
+
parameters: z20.object({
|
|
3501
|
+
url: z20.string(),
|
|
3502
|
+
limit: z20.number().default(10),
|
|
3503
|
+
maxDepth: z20.number().default(3),
|
|
3504
|
+
includePaths: z20.array(z20.string()).optional(),
|
|
3505
|
+
excludePaths: z20.array(z20.string()).optional(),
|
|
3506
|
+
allowSubdomains: z20.boolean().default(false),
|
|
3507
|
+
allowExternalLinks: z20.boolean().default(false),
|
|
3508
|
+
allowBackwardCrawling: z20.boolean().default(false),
|
|
3509
|
+
enableSpa: z20.boolean().default(true).describe("Disable to skip SPA route detection"),
|
|
3510
|
+
format: z20.enum(["markdown", "html"]).default("markdown"),
|
|
3511
|
+
onlyMainContent: z20.boolean().default(true),
|
|
3512
|
+
concurrency: z20.number().default(3),
|
|
3513
|
+
retries: z20.number().default(2),
|
|
3514
|
+
verbose: z20.boolean().default(false)
|
|
3052
3515
|
}),
|
|
3053
3516
|
handler: async (p, ctx) => {
|
|
3054
3517
|
const startUrl = new URL(p.url);
|
|
@@ -3187,7 +3650,7 @@ var crawlCommand = registerCommand({
|
|
|
3187
3650
|
if (errorPages.length > 0) {
|
|
3188
3651
|
response.errors = errorPages;
|
|
3189
3652
|
}
|
|
3190
|
-
return
|
|
3653
|
+
return ok20(response);
|
|
3191
3654
|
} finally {
|
|
3192
3655
|
for (const ctx2 of contexts) {
|
|
3193
3656
|
await ctx2.close().catch(() => {
|
|
@@ -3197,15 +3660,15 @@ var crawlCommand = registerCommand({
|
|
|
3197
3660
|
} catch (err) {
|
|
3198
3661
|
const message = err instanceof Error ? err.message : String(err);
|
|
3199
3662
|
const successPages = results.filter((r) => !isPageError(r));
|
|
3200
|
-
return
|
|
3663
|
+
return ok20({ pages: successPages, total: successPages.length, success: false, error: message });
|
|
3201
3664
|
}
|
|
3202
3665
|
}
|
|
3203
3666
|
});
|
|
3204
3667
|
|
|
3205
3668
|
// src/commands/search.ts
|
|
3206
|
-
import { z as
|
|
3669
|
+
import { z as z21 } from "zod";
|
|
3207
3670
|
import * as cheerio2 from "cheerio";
|
|
3208
|
-
import { ok as
|
|
3671
|
+
import { ok as ok21 } from "@dyyz1993/xcli-core";
|
|
3209
3672
|
function getRecencyParams(recency) {
|
|
3210
3673
|
const now = Math.floor(Date.now() / 1e3);
|
|
3211
3674
|
switch (recency) {
|
|
@@ -3539,16 +4002,16 @@ var searchCommand = registerCommand({
|
|
|
3539
4002
|
name: "search",
|
|
3540
4003
|
description: "Search the web and extract results with engine fallback",
|
|
3541
4004
|
scope: "project",
|
|
3542
|
-
parameters:
|
|
3543
|
-
query:
|
|
3544
|
-
engine:
|
|
3545
|
-
limit:
|
|
3546
|
-
full:
|
|
3547
|
-
format:
|
|
3548
|
-
timeout:
|
|
3549
|
-
recency:
|
|
3550
|
-
fallback:
|
|
3551
|
-
site:
|
|
4005
|
+
parameters: z21.object({
|
|
4006
|
+
query: z21.string(),
|
|
4007
|
+
engine: z21.string().optional(),
|
|
4008
|
+
limit: z21.number().default(10),
|
|
4009
|
+
full: z21.boolean().default(false),
|
|
4010
|
+
format: z21.enum(["markdown", "json", "text"]).default("markdown"),
|
|
4011
|
+
timeout: z21.number().default(15e3),
|
|
4012
|
+
recency: z21.enum(["hour", "day", "week", "month", "year"]).optional().describe("Filter by time: hour/day/week/month/year"),
|
|
4013
|
+
fallback: z21.boolean().default(false).describe("Sequential engine fallback instead of parallel"),
|
|
4014
|
+
site: z21.string().optional().describe("Limit results to a specific site (e.g. github.com, v2ex.com)")
|
|
3552
4015
|
}),
|
|
3553
4016
|
handler: async (p, ctx) => {
|
|
3554
4017
|
const { context } = await createEphemeralContext(resolveLaunchOpts(ctx));
|
|
@@ -3654,7 +4117,7 @@ ${errors.map((e) => ` - ${e.engine}: ${e.error}`).join("\n")}`
|
|
|
3654
4117
|
lines.push(`> ${r.snippet}`);
|
|
3655
4118
|
lines.push("");
|
|
3656
4119
|
}
|
|
3657
|
-
return
|
|
4120
|
+
return ok21({ ...searchResult, content: lines.join("\n") });
|
|
3658
4121
|
}
|
|
3659
4122
|
if (p.format === "text") {
|
|
3660
4123
|
const lines = [`Search: ${searchResult.query} (Engine: ${searchResult.engine}, Total: ${searchResult.total})`, ""];
|
|
@@ -3664,9 +4127,9 @@ ${errors.map((e) => ` - ${e.engine}: ${e.error}`).join("\n")}`
|
|
|
3664
4127
|
lines.push(` ${r.snippet}`);
|
|
3665
4128
|
lines.push("");
|
|
3666
4129
|
}
|
|
3667
|
-
return
|
|
4130
|
+
return ok21({ ...searchResult, content: lines.join("\n") });
|
|
3668
4131
|
}
|
|
3669
|
-
return
|
|
4132
|
+
return ok21(searchResult);
|
|
3670
4133
|
} finally {
|
|
3671
4134
|
await closeEphemeralContext(context);
|
|
3672
4135
|
}
|
|
@@ -3674,8 +4137,8 @@ ${errors.map((e) => ` - ${e.engine}: ${e.error}`).join("\n")}`
|
|
|
3674
4137
|
});
|
|
3675
4138
|
|
|
3676
4139
|
// src/commands/network.ts
|
|
3677
|
-
import { z as
|
|
3678
|
-
import { ok as
|
|
4140
|
+
import { z as z22 } from "zod";
|
|
4141
|
+
import { ok as ok22, fail as fail9 } from "@dyyz1993/xcli-core";
|
|
3679
4142
|
function extractPath2(url) {
|
|
3680
4143
|
try {
|
|
3681
4144
|
const u = new URL(url);
|
|
@@ -3773,19 +4236,19 @@ var networkCommand = registerCommand({
|
|
|
3773
4236
|
name: "network",
|
|
3774
4237
|
description: "Capture and filter network responses from a URL",
|
|
3775
4238
|
scope: "project",
|
|
3776
|
-
parameters:
|
|
3777
|
-
url:
|
|
3778
|
-
filter:
|
|
3779
|
-
match:
|
|
3780
|
-
search:
|
|
3781
|
-
console:
|
|
3782
|
-
timeout:
|
|
3783
|
-
wait:
|
|
3784
|
-
limit:
|
|
3785
|
-
format:
|
|
3786
|
-
ws:
|
|
3787
|
-
listen:
|
|
3788
|
-
duration:
|
|
4239
|
+
parameters: z22.object({
|
|
4240
|
+
url: z22.string(),
|
|
4241
|
+
filter: z22.string().optional(),
|
|
4242
|
+
match: z22.string().optional(),
|
|
4243
|
+
search: z22.string().optional().describe("Search within all captured response bodies"),
|
|
4244
|
+
console: z22.boolean().default(false),
|
|
4245
|
+
timeout: z22.number().default(3e4),
|
|
4246
|
+
wait: z22.number().default(3e3),
|
|
4247
|
+
limit: z22.number().default(50),
|
|
4248
|
+
format: z22.enum(["summary", "json"]).default("summary"),
|
|
4249
|
+
ws: z22.boolean().optional().default(false).describe("Only show WebSocket messages"),
|
|
4250
|
+
listen: z22.boolean().optional().default(false).describe("\u76D1\u542C\u6A21\u5F0F\uFF1A\u4F7F\u7528\u73B0\u6709 session \u7684 page\uFF0C\u7B49\u5F85\u6307\u5B9A\u65F6\u95F4\u6355\u83B7\u8BF7\u6C42"),
|
|
4251
|
+
duration: z22.number().int().optional().default(15e3).describe("\u76D1\u542C\u6A21\u5F0F\u7B49\u5F85\u65F6\u957F\uFF08\u6BEB\u79D2\uFF0C\u9ED8\u8BA4 15000\uFF09")
|
|
3789
4252
|
}),
|
|
3790
4253
|
handler: async (p, ctx) => {
|
|
3791
4254
|
const startTime = Date.now();
|
|
@@ -3840,7 +4303,7 @@ var networkCommand = registerCommand({
|
|
|
3840
4303
|
};
|
|
3841
4304
|
if (p.listen) {
|
|
3842
4305
|
const page2 = ctx.page;
|
|
3843
|
-
if (!page2) return
|
|
4306
|
+
if (!page2) return fail9("No active page. Use --cdp to connect first.");
|
|
3844
4307
|
const captures = [];
|
|
3845
4308
|
const consoleMessages = [];
|
|
3846
4309
|
const wsCaptures = [];
|
|
@@ -3867,7 +4330,7 @@ var networkCommand = registerCommand({
|
|
|
3867
4330
|
page2.off("response", handler);
|
|
3868
4331
|
const duration = Date.now() - startTime;
|
|
3869
4332
|
if (p.ws && wsCaptures.length > 0) {
|
|
3870
|
-
return
|
|
4333
|
+
return ok22({
|
|
3871
4334
|
url: "[listen mode]",
|
|
3872
4335
|
duration,
|
|
3873
4336
|
total: captures.length,
|
|
@@ -3881,9 +4344,9 @@ var networkCommand = registerCommand({
|
|
|
3881
4344
|
});
|
|
3882
4345
|
}
|
|
3883
4346
|
if (p.format === "json") {
|
|
3884
|
-
return
|
|
4347
|
+
return ok22(buildJsonOutput("[listen mode]", captures, consoleMessages, captures.length, wsCaptures));
|
|
3885
4348
|
}
|
|
3886
|
-
return
|
|
4349
|
+
return ok22(buildSummaryOutput("[listen mode]", duration, captures, consoleMessages, captures.length, wsCaptures));
|
|
3887
4350
|
}
|
|
3888
4351
|
const { context, page } = await createEphemeralContext(resolveLaunchOpts(ctx));
|
|
3889
4352
|
try {
|
|
@@ -3938,7 +4401,7 @@ var networkCommand = registerCommand({
|
|
|
3938
4401
|
}
|
|
3939
4402
|
const duration = Date.now() - startTime;
|
|
3940
4403
|
if (p.ws && wsCaptures.length > 0) {
|
|
3941
|
-
return
|
|
4404
|
+
return ok22({
|
|
3942
4405
|
url: p.url,
|
|
3943
4406
|
duration,
|
|
3944
4407
|
total: captures.length,
|
|
@@ -3952,9 +4415,9 @@ var networkCommand = registerCommand({
|
|
|
3952
4415
|
});
|
|
3953
4416
|
}
|
|
3954
4417
|
if (p.format === "json") {
|
|
3955
|
-
return
|
|
4418
|
+
return ok22(buildJsonOutput(p.url, results, consoleMessages, totalCount, wsCaptures, searchResults));
|
|
3956
4419
|
}
|
|
3957
|
-
return
|
|
4420
|
+
return ok22(buildSummaryOutput(p.url, duration, results, consoleMessages, totalCount, wsCaptures));
|
|
3958
4421
|
} finally {
|
|
3959
4422
|
await closeEphemeralContext(context);
|
|
3960
4423
|
}
|
|
@@ -3962,7 +4425,7 @@ var networkCommand = registerCommand({
|
|
|
3962
4425
|
});
|
|
3963
4426
|
|
|
3964
4427
|
// src/commands/ai-search-engines.ts
|
|
3965
|
-
import { z as
|
|
4428
|
+
import { z as z23 } from "zod";
|
|
3966
4429
|
var ENGINE_CONFIGS = {
|
|
3967
4430
|
deepseek: {
|
|
3968
4431
|
key: "deepseek",
|
|
@@ -4216,7 +4679,7 @@ var ENGINE_CONFIGS = {
|
|
|
4216
4679
|
}
|
|
4217
4680
|
};
|
|
4218
4681
|
var ALL_ENGINE_KEYS = Object.keys(ENGINE_CONFIGS);
|
|
4219
|
-
var ENGINE_KEY_ENUM =
|
|
4682
|
+
var ENGINE_KEY_ENUM = z23.enum(ALL_ENGINE_KEYS);
|
|
4220
4683
|
function getEngineConfig(key) {
|
|
4221
4684
|
return ENGINE_CONFIGS[key];
|
|
4222
4685
|
}
|
|
@@ -4496,8 +4959,8 @@ function parseMarkdownResults(rawText) {
|
|
|
4496
4959
|
}
|
|
4497
4960
|
|
|
4498
4961
|
// src/commands/snapshot.ts
|
|
4499
|
-
import { z as
|
|
4500
|
-
import { ok as
|
|
4962
|
+
import { z as z24 } from "zod";
|
|
4963
|
+
import { ok as ok23, fail as fail10, normalizeTips as normalizeTips3 } from "@dyyz1993/xcli-core";
|
|
4501
4964
|
|
|
4502
4965
|
// src/runtime/ref-store.ts
|
|
4503
4966
|
var sessions = /* @__PURE__ */ new Map();
|
|
@@ -4738,8 +5201,8 @@ async function resolveRefParams(page, params, selectorKeys, cache, sessionId) {
|
|
|
4738
5201
|
}
|
|
4739
5202
|
|
|
4740
5203
|
// src/utils/site-semantics.ts
|
|
4741
|
-
import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync, readFileSync } from "fs";
|
|
4742
|
-
import { join as
|
|
5204
|
+
import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
5205
|
+
import { join as join6, dirname as dirname2 } from "path";
|
|
4743
5206
|
import { homedir as homedir4 } from "os";
|
|
4744
5207
|
import { stringify, parse } from "yaml";
|
|
4745
5208
|
import { execFile } from "child_process";
|
|
@@ -4817,10 +5280,10 @@ function inferAction(role, label) {
|
|
|
4817
5280
|
return {};
|
|
4818
5281
|
}
|
|
4819
5282
|
function getSemanticsDir() {
|
|
4820
|
-
return
|
|
5283
|
+
return join6(homedir4(), ".xbrowser", "site-semantics");
|
|
4821
5284
|
}
|
|
4822
5285
|
function getSemanticsPath(domain) {
|
|
4823
|
-
return
|
|
5286
|
+
return join6(getSemanticsDir(), `${domain}.yaml`);
|
|
4824
5287
|
}
|
|
4825
5288
|
function extractDomain2(url) {
|
|
4826
5289
|
try {
|
|
@@ -4834,9 +5297,9 @@ function saveSemantics(domain, pagePath, url, elements) {
|
|
|
4834
5297
|
const filePath = getSemanticsPath(domain);
|
|
4835
5298
|
const dir = dirname2(filePath);
|
|
4836
5299
|
let site;
|
|
4837
|
-
if (
|
|
5300
|
+
if (existsSync3(filePath)) {
|
|
4838
5301
|
try {
|
|
4839
|
-
site = parse(
|
|
5302
|
+
site = parse(readFileSync3(filePath, "utf-8"));
|
|
4840
5303
|
} catch {
|
|
4841
5304
|
site = { site: domain, pages: {} };
|
|
4842
5305
|
}
|
|
@@ -4856,16 +5319,16 @@ function saveSemantics(domain, pagePath, url, elements) {
|
|
|
4856
5319
|
}
|
|
4857
5320
|
}
|
|
4858
5321
|
site.updated_at = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
4859
|
-
if (!
|
|
5322
|
+
if (!existsSync3(dir)) {
|
|
4860
5323
|
mkdirSync3(dir, { recursive: true });
|
|
4861
5324
|
}
|
|
4862
5325
|
writeFileSync3(filePath, stringify(site, { lineWidth: 0 }));
|
|
4863
5326
|
}
|
|
4864
5327
|
function loadSemantics(domain) {
|
|
4865
5328
|
const filePath = getSemanticsPath(domain);
|
|
4866
|
-
if (!
|
|
5329
|
+
if (!existsSync3(filePath)) return null;
|
|
4867
5330
|
try {
|
|
4868
|
-
return parse(
|
|
5331
|
+
return parse(readFileSync3(filePath, "utf-8"));
|
|
4869
5332
|
} catch {
|
|
4870
5333
|
return null;
|
|
4871
5334
|
}
|
|
@@ -5409,24 +5872,24 @@ var snapshotCommand = registerCommand({
|
|
|
5409
5872
|
description: "Capture a quick page state snapshot \u2014 aria tree, visible text, or DOM summary",
|
|
5410
5873
|
scope: "page",
|
|
5411
5874
|
selectorParams: ["selector"],
|
|
5412
|
-
parameters:
|
|
5413
|
-
type:
|
|
5414
|
-
selector:
|
|
5415
|
-
depth:
|
|
5416
|
-
interactive:
|
|
5417
|
-
interactiveOnly:
|
|
5418
|
-
i:
|
|
5419
|
-
compact:
|
|
5420
|
-
c:
|
|
5421
|
-
selectors:
|
|
5422
|
-
all:
|
|
5875
|
+
parameters: z24.object({
|
|
5876
|
+
type: z24.enum(["aria", "text", "dom", "all"]).default("aria").describe("Snapshot type: aria (accessibility tree), text (visible text), dom (element summary), all (combined)"),
|
|
5877
|
+
selector: z24.string().optional().describe("Scope to a specific element"),
|
|
5878
|
+
depth: z24.number().optional().default(6).describe("Max depth for DOM/aria tree"),
|
|
5879
|
+
interactive: z24.boolean().optional().default(false).describe("Return interactive agent refs only"),
|
|
5880
|
+
interactiveOnly: z24.boolean().optional().default(false).describe("Alias for interactive"),
|
|
5881
|
+
i: z24.boolean().optional().default(false).describe("Short alias for interactive"),
|
|
5882
|
+
compact: z24.boolean().optional().default(false).describe("Include compact xbrowser style snapshot text"),
|
|
5883
|
+
c: z24.boolean().optional().default(false).describe("Short alias for compact"),
|
|
5884
|
+
selectors: z24.boolean().optional().default(false).describe("Include ref to CSS selector map"),
|
|
5885
|
+
all: z24.boolean().optional().default(false).describe("Include hidden interactive targets when using interactive snapshot")
|
|
5423
5886
|
}),
|
|
5424
|
-
result:
|
|
5425
|
-
url:
|
|
5426
|
-
title:
|
|
5427
|
-
aria:
|
|
5428
|
-
text:
|
|
5429
|
-
dom:
|
|
5887
|
+
result: z24.object({
|
|
5888
|
+
url: z24.string(),
|
|
5889
|
+
title: z24.string(),
|
|
5890
|
+
aria: z24.string().optional(),
|
|
5891
|
+
text: z24.string().optional(),
|
|
5892
|
+
dom: z24.record(z24.unknown()).optional()
|
|
5430
5893
|
}),
|
|
5431
5894
|
handler: async (p, ctx) => {
|
|
5432
5895
|
const page = ctx.page;
|
|
@@ -5440,7 +5903,7 @@ var snapshotCommand = registerCommand({
|
|
|
5440
5903
|
if (p.compact || p.c || p.interactive || p.interactiveOnly || p.i) {
|
|
5441
5904
|
observation.compact = formatObservationCompact(observation, { selectors: p.selectors });
|
|
5442
5905
|
}
|
|
5443
|
-
return
|
|
5906
|
+
return ok23(observation, normalizeTips3([
|
|
5444
5907
|
`refs refreshed for ${observation.targets.length} targets; use click @e1 or fill @e2 "text"`
|
|
5445
5908
|
]));
|
|
5446
5909
|
}
|
|
@@ -5448,15 +5911,15 @@ var snapshotCommand = registerCommand({
|
|
|
5448
5911
|
const aria = await captureAriaSnapshot(page, p.selector, p.depth);
|
|
5449
5912
|
const tips = await buildRefTips(page, aria);
|
|
5450
5913
|
persistSemantics(url, aria);
|
|
5451
|
-
return
|
|
5914
|
+
return ok23({ url, title, aria }, normalizeTips3(tips));
|
|
5452
5915
|
}
|
|
5453
5916
|
if (p.type === "text") {
|
|
5454
5917
|
const text = await captureTextSnapshot(page, p.selector);
|
|
5455
|
-
return
|
|
5918
|
+
return ok23({ url, title, text });
|
|
5456
5919
|
}
|
|
5457
5920
|
if (p.type === "dom") {
|
|
5458
5921
|
const dom = await captureDomSnapshot(page, p.selector, p.depth ?? 6);
|
|
5459
|
-
return
|
|
5922
|
+
return ok23({ url, title, dom });
|
|
5460
5923
|
}
|
|
5461
5924
|
if (p.type === "all") {
|
|
5462
5925
|
const [aria, text, dom] = await Promise.all([
|
|
@@ -5466,9 +5929,9 @@ var snapshotCommand = registerCommand({
|
|
|
5466
5929
|
]);
|
|
5467
5930
|
const tips = await buildRefTips(page, aria);
|
|
5468
5931
|
persistSemantics(url, aria);
|
|
5469
|
-
return
|
|
5932
|
+
return ok23({ url, title, aria, text, dom }, normalizeTips3(tips));
|
|
5470
5933
|
}
|
|
5471
|
-
return
|
|
5934
|
+
return fail10(`Unknown snapshot type: ${p.type}`);
|
|
5472
5935
|
}
|
|
5473
5936
|
});
|
|
5474
5937
|
function persistSemantics(url, aria) {
|
|
@@ -5545,22 +6008,22 @@ async function captureDomSnapshot(page, selector, maxDepth) {
|
|
|
5545
6008
|
}
|
|
5546
6009
|
|
|
5547
6010
|
// src/commands/agent.ts
|
|
5548
|
-
import { z as
|
|
5549
|
-
import { ok as
|
|
6011
|
+
import { z as z25 } from "zod";
|
|
6012
|
+
import { ok as ok24, normalizeTips as normalizeTips4 } from "@dyyz1993/xcli-core";
|
|
5550
6013
|
var observeCommand = registerCommand({
|
|
5551
6014
|
name: "observe",
|
|
5552
6015
|
description: "Observe the current page as structured agent targets with session refs",
|
|
5553
6016
|
scope: "page",
|
|
5554
|
-
parameters:
|
|
5555
|
-
includeHidden:
|
|
5556
|
-
limit:
|
|
5557
|
-
compact:
|
|
5558
|
-
selectors:
|
|
6017
|
+
parameters: z25.object({
|
|
6018
|
+
includeHidden: z25.boolean().optional().default(false).describe("Include hidden elements in the target list"),
|
|
6019
|
+
limit: z25.number().int().positive().max(300).optional().default(80).describe("Maximum number of targets to return"),
|
|
6020
|
+
compact: z25.boolean().optional().default(false).describe("Include compact xbrowser style snapshot text"),
|
|
6021
|
+
selectors: z25.boolean().optional().default(false).describe("Include ref to stable CSS selector map")
|
|
5559
6022
|
}),
|
|
5560
|
-
result:
|
|
5561
|
-
targets:
|
|
5562
|
-
selectors:
|
|
5563
|
-
compact:
|
|
6023
|
+
result: z25.object({
|
|
6024
|
+
targets: z25.array(z25.record(z25.unknown())),
|
|
6025
|
+
selectors: z25.record(z25.unknown()).optional(),
|
|
6026
|
+
compact: z25.string().optional()
|
|
5564
6027
|
}).passthrough(),
|
|
5565
6028
|
handler: async (p, ctx) => {
|
|
5566
6029
|
const observation = await observePage(ctx.page, ctx.sessionId, {
|
|
@@ -5569,7 +6032,7 @@ var observeCommand = registerCommand({
|
|
|
5569
6032
|
});
|
|
5570
6033
|
if (p.selectors) observation.selectors = buildSelectorMap(observation);
|
|
5571
6034
|
if (p.compact) observation.compact = formatObservationCompact(observation, { selectors: p.selectors });
|
|
5572
|
-
return
|
|
6035
|
+
return ok24(observation, normalizeTips4([
|
|
5573
6036
|
`refs refreshed for ${observation.targets.length} targets; use act --ref @e1 --action click or click @e1`
|
|
5574
6037
|
]));
|
|
5575
6038
|
}
|
|
@@ -5579,14 +6042,14 @@ var actCommand = registerCommand({
|
|
|
5579
6042
|
description: "Perform an agent action using an observe ref or explicit selector",
|
|
5580
6043
|
scope: "element",
|
|
5581
6044
|
selectorParams: ["selector"],
|
|
5582
|
-
parameters:
|
|
5583
|
-
action:
|
|
5584
|
-
ref:
|
|
5585
|
-
selector:
|
|
5586
|
-
value:
|
|
5587
|
-
key:
|
|
5588
|
-
force:
|
|
5589
|
-
timeout:
|
|
6045
|
+
parameters: z25.object({
|
|
6046
|
+
action: z25.enum(["click", "fill", "type", "press", "select", "check", "hover"]).default("click"),
|
|
6047
|
+
ref: z25.string().optional().describe("Session-scoped ref returned by observe, such as e1"),
|
|
6048
|
+
selector: z25.string().optional().describe("CSS selector fallback when no ref is available"),
|
|
6049
|
+
value: z25.string().optional().describe("Value for fill/type/select"),
|
|
6050
|
+
key: z25.string().optional().describe("Key for press"),
|
|
6051
|
+
force: z25.boolean().optional().default(false).describe("Bypass actionability checks"),
|
|
6052
|
+
timeout: z25.number().optional().default(1e4).describe("Playwright action timeout in milliseconds")
|
|
5590
6053
|
}).refine((p) => !!p.ref || !!p.selector, {
|
|
5591
6054
|
message: "Either ref or selector is required"
|
|
5592
6055
|
}),
|
|
@@ -5600,7 +6063,7 @@ var actCommand = registerCommand({
|
|
|
5600
6063
|
tips: normalizeTips4(result.stale ? ["run observe again to refresh refs"] : [])
|
|
5601
6064
|
};
|
|
5602
6065
|
}
|
|
5603
|
-
return
|
|
6066
|
+
return ok24(result, normalizeTips4(result.stale ? ["ref screen hash changed; run observe if the next action is uncertain"] : []));
|
|
5604
6067
|
}
|
|
5605
6068
|
});
|
|
5606
6069
|
var waitForCommand = registerCommand({
|
|
@@ -5608,16 +6071,16 @@ var waitForCommand = registerCommand({
|
|
|
5608
6071
|
description: "Wait for agent predicates such as text, URL, load state, selector state, or screen hash changes",
|
|
5609
6072
|
scope: "page",
|
|
5610
6073
|
selectorParams: ["selector"],
|
|
5611
|
-
parameters:
|
|
5612
|
-
selector:
|
|
5613
|
-
state:
|
|
5614
|
-
text:
|
|
5615
|
-
url:
|
|
5616
|
-
load:
|
|
5617
|
-
fn:
|
|
5618
|
-
screenHashChanged:
|
|
5619
|
-
timeout:
|
|
5620
|
-
pollInterval:
|
|
6074
|
+
parameters: z25.object({
|
|
6075
|
+
selector: z25.string().optional().describe("CSS selector or observe ref to wait for"),
|
|
6076
|
+
state: z25.enum(["attached", "detached", "visible", "hidden"]).optional().default("visible"),
|
|
6077
|
+
text: z25.string().optional().describe("Visible text to wait for"),
|
|
6078
|
+
url: z25.string().optional().describe("URL substring or glob pattern to wait for"),
|
|
6079
|
+
load: z25.enum(["load", "domcontentloaded", "networkidle"]).optional().describe("Load state to wait for"),
|
|
6080
|
+
fn: z25.string().optional().describe("JavaScript predicate to wait for"),
|
|
6081
|
+
screenHashChanged: z25.string().optional().describe("Previous screenHash from observe"),
|
|
6082
|
+
timeout: z25.number().optional().default(3e4),
|
|
6083
|
+
pollInterval: z25.number().optional().default(200)
|
|
5621
6084
|
}).refine((p) => [p.selector, p.text, p.url, p.load, p.fn, p.screenHashChanged].filter(Boolean).length === 1, {
|
|
5622
6085
|
message: "Provide exactly one wait predicate: selector, text, url, load, fn, or screenHashChanged"
|
|
5623
6086
|
}),
|
|
@@ -5631,30 +6094,30 @@ var waitForCommand = registerCommand({
|
|
|
5631
6094
|
tips: []
|
|
5632
6095
|
};
|
|
5633
6096
|
}
|
|
5634
|
-
return
|
|
6097
|
+
return ok24(result);
|
|
5635
6098
|
}
|
|
5636
6099
|
});
|
|
5637
6100
|
|
|
5638
6101
|
// src/commands/tab.ts
|
|
5639
|
-
import { z as
|
|
5640
|
-
import { ok as
|
|
5641
|
-
var TabParams =
|
|
5642
|
-
subcommand:
|
|
5643
|
-
url:
|
|
5644
|
-
index:
|
|
6102
|
+
import { z as z26 } from "zod";
|
|
6103
|
+
import { ok as ok25, fail as fail11 } from "@dyyz1993/xcli-core";
|
|
6104
|
+
var TabParams = z26.object({
|
|
6105
|
+
subcommand: z26.enum(["list", "new", "close", "switch"]),
|
|
6106
|
+
url: z26.string().optional(),
|
|
6107
|
+
index: z26.number().int().min(0).optional()
|
|
5645
6108
|
});
|
|
5646
6109
|
var tabCommand = registerCommand({
|
|
5647
6110
|
name: "tab",
|
|
5648
6111
|
description: "Manage browser tabs: list, new, close, switch",
|
|
5649
6112
|
scope: "page",
|
|
5650
6113
|
parameters: TabParams,
|
|
5651
|
-
result:
|
|
5652
|
-
success:
|
|
5653
|
-
data:
|
|
6114
|
+
result: z26.object({
|
|
6115
|
+
success: z26.boolean(),
|
|
6116
|
+
data: z26.unknown()
|
|
5654
6117
|
}),
|
|
5655
6118
|
handler: async (p, ctx) => {
|
|
5656
6119
|
if (!ctx.browserContext) {
|
|
5657
|
-
return
|
|
6120
|
+
return fail11("No browser context available. Use --cdp to connect to a browser first.");
|
|
5658
6121
|
}
|
|
5659
6122
|
const pages = ctx.browserContext.pages();
|
|
5660
6123
|
switch (p.subcommand) {
|
|
@@ -5667,7 +6130,7 @@ var tabCommand = registerCommand({
|
|
|
5667
6130
|
case "switch":
|
|
5668
6131
|
return handleSwitch(p, pages, ctx);
|
|
5669
6132
|
default:
|
|
5670
|
-
return
|
|
6133
|
+
return fail11(`Unknown subcommand: ${p.subcommand}`);
|
|
5671
6134
|
}
|
|
5672
6135
|
}
|
|
5673
6136
|
});
|
|
@@ -5682,7 +6145,7 @@ async function handleList(pages, ctx) {
|
|
|
5682
6145
|
if (isActive) activeIndex = i;
|
|
5683
6146
|
tabs.push({ index: i, url, title, active: isActive });
|
|
5684
6147
|
}
|
|
5685
|
-
return
|
|
6148
|
+
return ok25({ tabs, total: tabs.length, activeIndex });
|
|
5686
6149
|
}
|
|
5687
6150
|
async function handleNew(p, _pages, ctx) {
|
|
5688
6151
|
const newPage = await ctx.browserContext.newPage();
|
|
@@ -5710,7 +6173,7 @@ async function handleNew(p, _pages, ctx) {
|
|
|
5710
6173
|
const title = await newPage.title().catch(() => "");
|
|
5711
6174
|
const allPages = ctx.browserContext.pages();
|
|
5712
6175
|
const newIndex = allPages.indexOf(newPage);
|
|
5713
|
-
return
|
|
6176
|
+
return ok25({
|
|
5714
6177
|
index: newIndex >= 0 ? newIndex : allPages.length - 1,
|
|
5715
6178
|
url: newPage.url(),
|
|
5716
6179
|
title,
|
|
@@ -5721,11 +6184,11 @@ async function handleNew(p, _pages, ctx) {
|
|
|
5721
6184
|
async function handleClose(p, ctx) {
|
|
5722
6185
|
const currentPages = ctx.browserContext.pages();
|
|
5723
6186
|
if (currentPages.length <= 1) {
|
|
5724
|
-
return
|
|
6187
|
+
return fail11("Cannot close the last remaining tab");
|
|
5725
6188
|
}
|
|
5726
6189
|
const closeIndex = p.index ?? currentPages.findIndex((pg) => pg === ctx.page);
|
|
5727
6190
|
if (closeIndex < 0 || closeIndex >= currentPages.length) {
|
|
5728
|
-
return
|
|
6191
|
+
return fail11(`Invalid tab index: ${closeIndex}. Valid range: 0-${currentPages.length - 1}`);
|
|
5729
6192
|
}
|
|
5730
6193
|
const pageToClose = currentPages[closeIndex];
|
|
5731
6194
|
const isActivePage = pageToClose === ctx.page;
|
|
@@ -5740,7 +6203,7 @@ async function handleClose(p, ctx) {
|
|
|
5740
6203
|
}
|
|
5741
6204
|
ctx.page = newActivePage;
|
|
5742
6205
|
}
|
|
5743
|
-
return
|
|
6206
|
+
return ok25({
|
|
5744
6207
|
closedIndex: closeIndex,
|
|
5745
6208
|
total: remainingPages.length,
|
|
5746
6209
|
activeIndex: isActivePage ? closeIndex < remainingPages.length ? closeIndex : remainingPages.length - 1 : remainingPages.findIndex((pg) => pg === ctx.page)
|
|
@@ -5748,21 +6211,32 @@ async function handleClose(p, ctx) {
|
|
|
5748
6211
|
}
|
|
5749
6212
|
async function handleSwitch(p, pages, ctx) {
|
|
5750
6213
|
if (p.index === void 0) {
|
|
5751
|
-
return
|
|
6214
|
+
return fail11("Parameter --index is required for switch subcommand");
|
|
5752
6215
|
}
|
|
5753
6216
|
if (p.index < 0 || p.index >= pages.length) {
|
|
5754
|
-
return
|
|
6217
|
+
return fail11(`Invalid tab index: ${p.index}. Valid range: 0-${pages.length - 1}`);
|
|
5755
6218
|
}
|
|
5756
6219
|
const targetPage = pages[p.index];
|
|
5757
6220
|
await targetPage.bringToFront().catch(() => {
|
|
5758
6221
|
});
|
|
6222
|
+
for (let i = 0; i < 5; i++) {
|
|
6223
|
+
const okProbe = await targetPage.evaluate("1").then(() => true, () => false);
|
|
6224
|
+
if (okProbe) break;
|
|
6225
|
+
await new Promise((r) => setTimeout(r, 300));
|
|
6226
|
+
}
|
|
6227
|
+
try {
|
|
6228
|
+
const mouse = targetPage.mouse;
|
|
6229
|
+
if (mouse) await mouse.move(1, 1).catch(() => {
|
|
6230
|
+
});
|
|
6231
|
+
} catch {
|
|
6232
|
+
}
|
|
5759
6233
|
const session = ctx.sessionId ? getSessionById(ctx.sessionId) : void 0;
|
|
5760
6234
|
if (session) {
|
|
5761
6235
|
setActivePage(session, targetPage);
|
|
5762
6236
|
}
|
|
5763
6237
|
ctx.page = targetPage;
|
|
5764
6238
|
const title = await targetPage.title().catch(() => "");
|
|
5765
|
-
return
|
|
6239
|
+
return ok25({
|
|
5766
6240
|
index: p.index,
|
|
5767
6241
|
url: targetPage.url(),
|
|
5768
6242
|
title,
|
|
@@ -5771,9 +6245,9 @@ async function handleSwitch(p, pages, ctx) {
|
|
|
5771
6245
|
}
|
|
5772
6246
|
|
|
5773
6247
|
// src/commands/addinitscript.ts
|
|
5774
|
-
import { z as
|
|
5775
|
-
import { ok as
|
|
5776
|
-
import { readFileSync as
|
|
6248
|
+
import { z as z27 } from "zod";
|
|
6249
|
+
import { ok as ok26 } from "@dyyz1993/xcli-core";
|
|
6250
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
5777
6251
|
|
|
5778
6252
|
// src/chain-parser.ts
|
|
5779
6253
|
import { splitCommand, parseCommandArgs } from "@dyyz1993/xcli-core";
|
|
@@ -5913,14 +6387,14 @@ registerCommandDefinition("tab", ["subcommand"]);
|
|
|
5913
6387
|
registerCommandDefinition("mouse", ["action", "x", "y"]);
|
|
5914
6388
|
|
|
5915
6389
|
// src/commands/addinitscript.ts
|
|
5916
|
-
var InitScriptParams =
|
|
5917
|
-
script:
|
|
5918
|
-
file:
|
|
5919
|
-
stdin:
|
|
5920
|
-
name:
|
|
5921
|
-
list:
|
|
5922
|
-
remove:
|
|
5923
|
-
base64:
|
|
6390
|
+
var InitScriptParams = z27.object({
|
|
6391
|
+
script: z27.string().optional(),
|
|
6392
|
+
file: z27.string().optional(),
|
|
6393
|
+
stdin: z27.boolean().optional(),
|
|
6394
|
+
name: z27.string().optional(),
|
|
6395
|
+
list: z27.boolean().optional(),
|
|
6396
|
+
remove: z27.union([z27.string(), z27.boolean()]).optional(),
|
|
6397
|
+
base64: z27.string().optional()
|
|
5924
6398
|
});
|
|
5925
6399
|
var registeredScripts = /* @__PURE__ */ new Map();
|
|
5926
6400
|
function resolveScriptContent(params) {
|
|
@@ -5928,7 +6402,7 @@ function resolveScriptContent(params) {
|
|
|
5928
6402
|
return Buffer.from(params.base64, "base64").toString("utf-8");
|
|
5929
6403
|
}
|
|
5930
6404
|
if (params.file) {
|
|
5931
|
-
return
|
|
6405
|
+
return readFileSync4(params.file, "utf-8");
|
|
5932
6406
|
}
|
|
5933
6407
|
if (params.stdin) {
|
|
5934
6408
|
return null;
|
|
@@ -5954,14 +6428,14 @@ var addInitScriptCommand = registerCommand({
|
|
|
5954
6428
|
description: "Add an initialization script that runs on every page load",
|
|
5955
6429
|
scope: "page",
|
|
5956
6430
|
parameters: InitScriptParams,
|
|
5957
|
-
result:
|
|
5958
|
-
scripts:
|
|
5959
|
-
removed:
|
|
5960
|
-
existed:
|
|
5961
|
-
error:
|
|
5962
|
-
registered:
|
|
5963
|
-
hint:
|
|
5964
|
-
executedImmediately:
|
|
6431
|
+
result: z27.object({
|
|
6432
|
+
scripts: z27.array(z27.object({ name: z27.string(), size: z27.number(), preview: z27.string() })).optional(),
|
|
6433
|
+
removed: z27.string().optional(),
|
|
6434
|
+
existed: z27.boolean().optional(),
|
|
6435
|
+
error: z27.string().optional(),
|
|
6436
|
+
registered: z27.string().optional(),
|
|
6437
|
+
hint: z27.string().optional(),
|
|
6438
|
+
executedImmediately: z27.boolean().optional()
|
|
5965
6439
|
}).passthrough(),
|
|
5966
6440
|
handler: async (params, ctx) => {
|
|
5967
6441
|
if (params.list) {
|
|
@@ -5970,20 +6444,20 @@ var addInitScriptCommand = registerCommand({
|
|
|
5970
6444
|
size: content2.length,
|
|
5971
6445
|
preview: content2.slice(0, 80)
|
|
5972
6446
|
}));
|
|
5973
|
-
return
|
|
6447
|
+
return ok26({ scripts });
|
|
5974
6448
|
}
|
|
5975
6449
|
if (params.remove && typeof params.remove === "string") {
|
|
5976
6450
|
const existed = registeredScripts.delete(params.remove);
|
|
5977
|
-
return
|
|
6451
|
+
return ok26({ removed: params.remove, existed });
|
|
5978
6452
|
}
|
|
5979
6453
|
const removeTarget = (typeof params.remove === "string" ? params.remove : null) || (params.name && !resolveScriptContent(params) ? params.name : null);
|
|
5980
6454
|
if (removeTarget && !resolveScriptContent(params)) {
|
|
5981
6455
|
const existed = registeredScripts.delete(removeTarget);
|
|
5982
|
-
return
|
|
6456
|
+
return ok26({ removed: removeTarget, existed });
|
|
5983
6457
|
}
|
|
5984
6458
|
let content = params.stdin ? await readStdin() : resolveScriptContent(params);
|
|
5985
6459
|
if (!content) {
|
|
5986
|
-
return
|
|
6460
|
+
return ok26({ error: "No script content provided. Use --script, --file, --stdin, or --base64" });
|
|
5987
6461
|
}
|
|
5988
6462
|
const scriptName = params.name ?? `script-${Date.now()}`;
|
|
5989
6463
|
registeredScripts.set(scriptName, content);
|
|
@@ -5991,45 +6465,45 @@ var addInitScriptCommand = registerCommand({
|
|
|
5991
6465
|
try {
|
|
5992
6466
|
await ctx.page.evaluate(content);
|
|
5993
6467
|
} catch {
|
|
5994
|
-
return
|
|
6468
|
+
return ok26({
|
|
5995
6469
|
registered: scriptName,
|
|
5996
6470
|
hint: "Script registered for future page loads; immediate execution skipped (page may not be ready)"
|
|
5997
6471
|
});
|
|
5998
6472
|
}
|
|
5999
|
-
return
|
|
6473
|
+
return ok26({ registered: scriptName, executedImmediately: true });
|
|
6000
6474
|
}
|
|
6001
6475
|
});
|
|
6002
6476
|
registerCommandDefinition("addinitscript", ["script"]);
|
|
6003
6477
|
|
|
6004
6478
|
// src/commands/find.ts
|
|
6005
|
-
import { z as
|
|
6006
|
-
import { ok as
|
|
6007
|
-
var actionSchema2 =
|
|
6479
|
+
import { z as z28 } from "zod";
|
|
6480
|
+
import { ok as ok27, fail as fail12, normalizeTips as normalizeTips5 } from "@dyyz1993/xcli-core";
|
|
6481
|
+
var actionSchema2 = z28.enum(["click", "fill", "type", "select", "hover", "check"]);
|
|
6008
6482
|
var findCommand = registerCommand({
|
|
6009
6483
|
name: "find",
|
|
6010
6484
|
description: "Find elements by semantic strategy (text/role/label/placeholder/testid) and optionally perform an action",
|
|
6011
6485
|
scope: "page",
|
|
6012
|
-
parameters:
|
|
6013
|
-
strategy:
|
|
6014
|
-
value:
|
|
6015
|
-
name:
|
|
6016
|
-
exact:
|
|
6017
|
-
operation:
|
|
6486
|
+
parameters: z28.object({
|
|
6487
|
+
strategy: z28.enum(["text", "role", "label", "placeholder", "testid", "alt", "title", "first", "last", "nth"]),
|
|
6488
|
+
value: z28.string(),
|
|
6489
|
+
name: z28.string().optional(),
|
|
6490
|
+
exact: z28.boolean().optional().default(false),
|
|
6491
|
+
operation: z28.string().optional().describe('Trailing operation syntax, e.g. click, fill "text", type "text"'),
|
|
6018
6492
|
action: actionSchema2.optional().describe("Action to perform when not using trailing operation syntax"),
|
|
6019
|
-
actionValue:
|
|
6020
|
-
index:
|
|
6021
|
-
click:
|
|
6022
|
-
fill:
|
|
6023
|
-
type:
|
|
6024
|
-
select:
|
|
6025
|
-
hover:
|
|
6026
|
-
check:
|
|
6027
|
-
timeout:
|
|
6493
|
+
actionValue: z28.string().optional().describe("Value for fill/type/select when using action"),
|
|
6494
|
+
index: z28.number().int().optional().describe("Index for nth strategy"),
|
|
6495
|
+
click: z28.boolean().optional().default(false),
|
|
6496
|
+
fill: z28.string().optional(),
|
|
6497
|
+
type: z28.string().optional(),
|
|
6498
|
+
select: z28.string().optional(),
|
|
6499
|
+
hover: z28.boolean().optional().default(false),
|
|
6500
|
+
check: z28.boolean().optional().default(false),
|
|
6501
|
+
timeout: z28.number().optional().default(1e4)
|
|
6028
6502
|
}),
|
|
6029
|
-
result:
|
|
6030
|
-
matched:
|
|
6031
|
-
selector:
|
|
6032
|
-
action:
|
|
6503
|
+
result: z28.object({
|
|
6504
|
+
matched: z28.number(),
|
|
6505
|
+
selector: z28.string(),
|
|
6506
|
+
action: z28.string().optional()
|
|
6033
6507
|
}),
|
|
6034
6508
|
handler: async (p, ctx) => {
|
|
6035
6509
|
const page = ctx.page;
|
|
@@ -6044,7 +6518,7 @@ var findCommand = registerCommand({
|
|
|
6044
6518
|
});
|
|
6045
6519
|
const count = await locator.count();
|
|
6046
6520
|
if (count === 0) {
|
|
6047
|
-
return
|
|
6521
|
+
return fail12(`No element found with ${p.strategy}="${p.value}"`);
|
|
6048
6522
|
}
|
|
6049
6523
|
const tips = [];
|
|
6050
6524
|
const target = selectTarget(locator, p.strategy);
|
|
@@ -6056,15 +6530,15 @@ var findCommand = registerCommand({
|
|
|
6056
6530
|
await target.click({ timeout: p.timeout, force: true });
|
|
6057
6531
|
return okWithTips({ matched: count, selector, action: "click" }, tips);
|
|
6058
6532
|
} else if (actionName === "fill") {
|
|
6059
|
-
if (actionValue === void 0) return
|
|
6533
|
+
if (actionValue === void 0) return fail12("find fill requires a value");
|
|
6060
6534
|
await target.fill(actionValue, { timeout: p.timeout, force: true });
|
|
6061
6535
|
return okWithTips({ matched: count, selector, action: `fill("${actionValue}")` }, tips);
|
|
6062
6536
|
} else if (actionName === "type") {
|
|
6063
|
-
if (actionValue === void 0) return
|
|
6537
|
+
if (actionValue === void 0) return fail12("find type requires a value");
|
|
6064
6538
|
await target.type(actionValue, { delay: 10, timeout: p.timeout });
|
|
6065
6539
|
return okWithTips({ matched: count, selector, action: `type("${actionValue}")` }, tips);
|
|
6066
6540
|
} else if (actionName === "select") {
|
|
6067
|
-
if (actionValue === void 0) return
|
|
6541
|
+
if (actionValue === void 0) return fail12("find select requires a value");
|
|
6068
6542
|
await target.selectOption(actionValue);
|
|
6069
6543
|
return okWithTips({ matched: count, selector, action: `select("${actionValue}")` }, tips);
|
|
6070
6544
|
} else if (actionName === "hover") {
|
|
@@ -6078,7 +6552,7 @@ var findCommand = registerCommand({
|
|
|
6078
6552
|
}
|
|
6079
6553
|
});
|
|
6080
6554
|
function okWithTips(data, tips) {
|
|
6081
|
-
const result =
|
|
6555
|
+
const result = ok27(data);
|
|
6082
6556
|
if (tips.length > 0) result.tips = normalizeTips5(tips);
|
|
6083
6557
|
return result;
|
|
6084
6558
|
}
|
|
@@ -6202,7 +6676,7 @@ function attachWaitForHuman(ctx, getOrCreateWSServer) {
|
|
|
6202
6676
|
if (!ctx.page) {
|
|
6203
6677
|
throw new Error("waitForHuman requires an active page");
|
|
6204
6678
|
}
|
|
6205
|
-
const { HumanInteractionManager: HumanInteractionManager2 } = await import("./human-interaction-
|
|
6679
|
+
const { HumanInteractionManager: HumanInteractionManager2 } = await import("./human-interaction-Y5IDH6LD.js");
|
|
6206
6680
|
const wsServer2 = await getOrCreateWSServer(ctx.browserContext);
|
|
6207
6681
|
const manager = new HumanInteractionManager2(wsServer2, ctx.page);
|
|
6208
6682
|
return manager.waitForHuman(options);
|
|
@@ -6210,7 +6684,7 @@ function attachWaitForHuman(ctx, getOrCreateWSServer) {
|
|
|
6210
6684
|
}
|
|
6211
6685
|
function attachDetectAntiBot(ctx) {
|
|
6212
6686
|
ctx.detectAntiBot = async (page, config) => {
|
|
6213
|
-
const { detectAntiBot: detectAntiBot2 } = await import("./anti-bot-
|
|
6687
|
+
const { detectAntiBot: detectAntiBot2 } = await import("./anti-bot-GTTYNEFB.js");
|
|
6214
6688
|
return detectAntiBot2(page, config);
|
|
6215
6689
|
};
|
|
6216
6690
|
}
|
|
@@ -6226,18 +6700,18 @@ import {
|
|
|
6226
6700
|
Core
|
|
6227
6701
|
} from "@dyyz1993/xcli-core";
|
|
6228
6702
|
import { resolve as resolve2 } from "path";
|
|
6229
|
-
import { existsSync as
|
|
6703
|
+
import { existsSync as existsSync6, readdirSync } from "fs";
|
|
6230
6704
|
import { homedir as homedir5 } from "os";
|
|
6231
6705
|
|
|
6232
6706
|
// src/plugin/metadata-parser.ts
|
|
6233
|
-
import { existsSync as
|
|
6707
|
+
import { existsSync as existsSync4 } from "fs";
|
|
6234
6708
|
import { resolve } from "path";
|
|
6235
6709
|
|
|
6236
6710
|
// src/utils/json-file.ts
|
|
6237
|
-
import { readFileSync as
|
|
6711
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
6238
6712
|
function readJsonFile(filePath, defaultValue) {
|
|
6239
6713
|
try {
|
|
6240
|
-
const content =
|
|
6714
|
+
const content = readFileSync5(filePath, "utf-8");
|
|
6241
6715
|
return JSON.parse(content);
|
|
6242
6716
|
} catch {
|
|
6243
6717
|
return defaultValue;
|
|
@@ -6249,7 +6723,7 @@ var PluginMetadataParser = class {
|
|
|
6249
6723
|
static XBROWSER_KEYWORDS = ["xbrowser", "xbrowser-plugin"];
|
|
6250
6724
|
static parseFromPackageJson(pluginPath) {
|
|
6251
6725
|
const packageJsonPath = resolve(pluginPath, "package.json");
|
|
6252
|
-
if (!
|
|
6726
|
+
if (!existsSync4(packageJsonPath)) {
|
|
6253
6727
|
return null;
|
|
6254
6728
|
}
|
|
6255
6729
|
const packageJson = readJsonFile(packageJsonPath, null);
|
|
@@ -6313,20 +6787,20 @@ var PluginMetadataParser = class {
|
|
|
6313
6787
|
};
|
|
6314
6788
|
|
|
6315
6789
|
// src/plugin/ensure-deps.ts
|
|
6316
|
-
import { existsSync as
|
|
6317
|
-
import { join as
|
|
6790
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync4, writeFileSync as writeFileSync5 } from "fs";
|
|
6791
|
+
import { join as join7 } from "path";
|
|
6318
6792
|
import { execSync } from "child_process";
|
|
6319
6793
|
var SHARED_PLUGIN_DEPENDENCIES = {
|
|
6320
6794
|
"zod": "^3.24.0",
|
|
6321
6795
|
"@dyyz1993/xcli-core": "^0.12.1"
|
|
6322
6796
|
};
|
|
6323
6797
|
function ensurePluginDependencies(pluginsDir) {
|
|
6324
|
-
const zodPath =
|
|
6325
|
-
if (
|
|
6798
|
+
const zodPath = join7(pluginsDir, "node_modules", "zod");
|
|
6799
|
+
if (existsSync5(zodPath)) return;
|
|
6326
6800
|
mkdirSync4(pluginsDir, { recursive: true });
|
|
6327
|
-
const pkgPath =
|
|
6801
|
+
const pkgPath = join7(pluginsDir, "package.json");
|
|
6328
6802
|
let pkg = {};
|
|
6329
|
-
if (
|
|
6803
|
+
if (existsSync5(pkgPath)) {
|
|
6330
6804
|
try {
|
|
6331
6805
|
pkg = readJsonFile(pkgPath, {});
|
|
6332
6806
|
} catch {
|
|
@@ -6340,7 +6814,7 @@ function ensurePluginDependencies(pluginsDir) {
|
|
|
6340
6814
|
needsInstall = true;
|
|
6341
6815
|
}
|
|
6342
6816
|
}
|
|
6343
|
-
if (!needsInstall &&
|
|
6817
|
+
if (!needsInstall && existsSync5(join7(pluginsDir, "node_modules"))) return;
|
|
6344
6818
|
pkg.dependencies = existingDeps;
|
|
6345
6819
|
pkg.private = true;
|
|
6346
6820
|
pkg.description = pkg.description || "xbrowser plugins \u2014 shared dependencies";
|
|
@@ -6598,7 +7072,7 @@ var XBrowserPluginLoader = class {
|
|
|
6598
7072
|
const loaded = [];
|
|
6599
7073
|
const seen = /* @__PURE__ */ new Set();
|
|
6600
7074
|
for (const dir of dirs) {
|
|
6601
|
-
if (!
|
|
7075
|
+
if (!existsSync6(dir)) continue;
|
|
6602
7076
|
const entries = readdirSync(dir, { withFileTypes: true });
|
|
6603
7077
|
for (const entry of entries) {
|
|
6604
7078
|
if (!entry.isDirectory()) continue;
|
|
@@ -6606,12 +7080,12 @@ var XBrowserPluginLoader = class {
|
|
|
6606
7080
|
seen.add(entry.name);
|
|
6607
7081
|
const pluginDir = resolve2(dir, entry.name);
|
|
6608
7082
|
let indexPath = resolve2(pluginDir, "index.js");
|
|
6609
|
-
if (!
|
|
7083
|
+
if (!existsSync6(indexPath)) {
|
|
6610
7084
|
indexPath = resolve2(pluginDir, "index.ts");
|
|
6611
7085
|
}
|
|
6612
|
-
if (!
|
|
7086
|
+
if (!existsSync6(indexPath)) continue;
|
|
6613
7087
|
try {
|
|
6614
|
-
if (!
|
|
7088
|
+
if (!existsSync6(resolve2(pluginDir, "package.json"))) {
|
|
6615
7089
|
console.warn(`\u26A0\uFE0F Plugin "${entry.name}" has no package.json. Use "xbrowser create ${entry.name} --template static" for proper structure.`);
|
|
6616
7090
|
} else {
|
|
6617
7091
|
const metadata = PluginMetadataParser.parseFromPackageJson(pluginDir);
|
|
@@ -7268,7 +7742,7 @@ async function loadHooks() {
|
|
|
7268
7742
|
|
|
7269
7743
|
// src/executor.ts
|
|
7270
7744
|
import { homedir as homedir6 } from "os";
|
|
7271
|
-
import { join as
|
|
7745
|
+
import { join as join8 } from "path";
|
|
7272
7746
|
function levenshtein(a, b) {
|
|
7273
7747
|
const m = a.length, n = b.length;
|
|
7274
7748
|
const dp = Array(m + 1).fill(null).map(() => Array(n + 1).fill(0));
|
|
@@ -7283,7 +7757,7 @@ function levenshtein(a, b) {
|
|
|
7283
7757
|
}
|
|
7284
7758
|
var NAVIGATION_COMMANDS = /* @__PURE__ */ new Set(["goto", "back", "forward", "refresh"]);
|
|
7285
7759
|
var snapshotHintShown = /* @__PURE__ */ new WeakSet();
|
|
7286
|
-
var CONFIG_DIR2 =
|
|
7760
|
+
var CONFIG_DIR2 = join8(homedir6(), ".xbrowser");
|
|
7287
7761
|
var storageCache = /* @__PURE__ */ new Map();
|
|
7288
7762
|
function getPluginStorage(pluginName) {
|
|
7289
7763
|
if (!storageCache.has(pluginName)) {
|
|
@@ -7295,7 +7769,7 @@ var archiveInitialized = false;
|
|
|
7295
7769
|
function ensureArchiveInit() {
|
|
7296
7770
|
if (!archiveInitialized) {
|
|
7297
7771
|
try {
|
|
7298
|
-
configureArchiveStore({ archiveDir:
|
|
7772
|
+
configureArchiveStore({ archiveDir: join8(homedir6(), ".xbrowser", "archives") });
|
|
7299
7773
|
} catch {
|
|
7300
7774
|
}
|
|
7301
7775
|
archiveInitialized = true;
|
|
@@ -7319,7 +7793,7 @@ async function guardCheck(commandName) {
|
|
|
7319
7793
|
}
|
|
7320
7794
|
}
|
|
7321
7795
|
function errorResult(message) {
|
|
7322
|
-
return { ...
|
|
7796
|
+
return { ...fail13(message), duration: 0 };
|
|
7323
7797
|
}
|
|
7324
7798
|
function tipsToMessages(tips) {
|
|
7325
7799
|
if (!tips || tips.length === 0) return [];
|
|
@@ -7399,7 +7873,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
7399
7873
|
}
|
|
7400
7874
|
let targetPageOverride = null;
|
|
7401
7875
|
if (_target && extraOpts?.cdpEndpoint) {
|
|
7402
|
-
const { findTargetPage } = await import("./browser-
|
|
7876
|
+
const { findTargetPage } = await import("./browser-3HQKDZQ6.js");
|
|
7403
7877
|
targetPageOverride = await findTargetPage(extraOpts.cdpEndpoint, _target);
|
|
7404
7878
|
if (!targetPageOverride) {
|
|
7405
7879
|
return errorResult(`Target "${_target}" not found. Use 'xbrowser targets --cdp ${extraOpts.cdpEndpoint}' to list available pages.`);
|
|
@@ -7416,8 +7890,15 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
7416
7890
|
params = result.data;
|
|
7417
7891
|
}
|
|
7418
7892
|
if (command.scope !== "cli" && !process.env.XBROWSER_DAEMON_WORKER) {
|
|
7419
|
-
const { forwardExec } = await import("./daemon-client-
|
|
7420
|
-
const
|
|
7893
|
+
const { forwardExec } = await import("./daemon-client-HEGAXWGK.js");
|
|
7894
|
+
const longRunning = /^(vision-task|crawl|scrape|record|replay)$/.test(commandName);
|
|
7895
|
+
const result = await forwardExec(
|
|
7896
|
+
commandName,
|
|
7897
|
+
params,
|
|
7898
|
+
sessionName,
|
|
7899
|
+
extraOpts?.cdpEndpoint,
|
|
7900
|
+
longRunning ? 20 * 6e4 : void 0
|
|
7901
|
+
);
|
|
7421
7902
|
if (result) return result;
|
|
7422
7903
|
}
|
|
7423
7904
|
let session;
|
|
@@ -7585,7 +8066,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
7585
8066
|
timestamp: start
|
|
7586
8067
|
});
|
|
7587
8068
|
if (isSuccess) {
|
|
7588
|
-
return { ...
|
|
8069
|
+
return { ...ok28(raw.data, merged.length > 0 ? merged : raw.tips), duration, ...hookOutputs ? { hookOutputs } : {} };
|
|
7589
8070
|
}
|
|
7590
8071
|
return { success: false, data: raw.data, message: raw.message, tips: mergedOrRaw, duration, ...hookOutputs ? { hookOutputs } : {} };
|
|
7591
8072
|
}
|
|
@@ -7599,14 +8080,14 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
7599
8080
|
duration,
|
|
7600
8081
|
timestamp: start
|
|
7601
8082
|
});
|
|
7602
|
-
return { ...
|
|
8083
|
+
return { ...ok28(raw, smartTipNormalized), duration, ...hookOutputs ? { hookOutputs } : {} };
|
|
7603
8084
|
} catch (err) {
|
|
7604
8085
|
const end = Date.now();
|
|
7605
8086
|
const duration = end - start;
|
|
7606
8087
|
const errorMessage = errMsg(err);
|
|
7607
8088
|
if (session?.page && process.env.XBROWSER_RECOVERY && !extraOpts?._recoveryAttempted) {
|
|
7608
8089
|
try {
|
|
7609
|
-
const { attemptRecovery } = await import("./recovery-
|
|
8090
|
+
const { attemptRecovery } = await import("./recovery-L5GLDX4O.js");
|
|
7610
8091
|
const recovery = await attemptRecovery(
|
|
7611
8092
|
session.page,
|
|
7612
8093
|
sessionName,
|
|
@@ -7644,7 +8125,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
7644
8125
|
duration,
|
|
7645
8126
|
timestamp: start
|
|
7646
8127
|
});
|
|
7647
|
-
return { ...
|
|
8128
|
+
return { ...fail13(errorMessage), duration };
|
|
7648
8129
|
} finally {
|
|
7649
8130
|
}
|
|
7650
8131
|
}
|
|
@@ -7685,7 +8166,7 @@ async function executeChain(input, options) {
|
|
|
7685
8166
|
results.push({
|
|
7686
8167
|
command: cmdName,
|
|
7687
8168
|
raw: cmdStr,
|
|
7688
|
-
...
|
|
8169
|
+
...fail13(`Plugin "${cmdName}" requires a sub-command`),
|
|
7689
8170
|
duration: 0
|
|
7690
8171
|
});
|
|
7691
8172
|
if (type === "and") {
|
|
@@ -7704,7 +8185,7 @@ async function executeChain(input, options) {
|
|
|
7704
8185
|
results.push({
|
|
7705
8186
|
command: cmdName,
|
|
7706
8187
|
raw: cmdStr,
|
|
7707
|
-
...
|
|
8188
|
+
...fail13(`Unknown command "${subCommand}" for plugin "${cmdName}"`),
|
|
7708
8189
|
duration: 0
|
|
7709
8190
|
});
|
|
7710
8191
|
if (type === "and") {
|
|
@@ -7808,7 +8289,7 @@ async function executeChain(input, options) {
|
|
|
7808
8289
|
results.push({
|
|
7809
8290
|
command: `${cmdName} ${subCommand}`,
|
|
7810
8291
|
raw: cmdStr,
|
|
7811
|
-
...
|
|
8292
|
+
...ok28(data),
|
|
7812
8293
|
duration: duration2,
|
|
7813
8294
|
...hookOutputs ? { hookOutputs } : {}
|
|
7814
8295
|
});
|
|
@@ -7836,7 +8317,7 @@ async function executeChain(input, options) {
|
|
|
7836
8317
|
results.push({
|
|
7837
8318
|
command: `${cmdName} ${subCommand}`,
|
|
7838
8319
|
raw: cmdStr,
|
|
7839
|
-
...
|
|
8320
|
+
...fail13(errorMessage),
|
|
7840
8321
|
duration: duration2
|
|
7841
8322
|
});
|
|
7842
8323
|
if (type === "and") {
|
|
@@ -8150,25 +8631,25 @@ Run "xbrowser config list" to see current keys.`
|
|
|
8150
8631
|
|
|
8151
8632
|
// src/plugin/installer.ts
|
|
8152
8633
|
import {
|
|
8153
|
-
existsSync as
|
|
8634
|
+
existsSync as existsSync12,
|
|
8154
8635
|
readdirSync as readdirSync2,
|
|
8155
8636
|
mkdirSync as mkdirSync8,
|
|
8156
8637
|
rmSync as rmSync6,
|
|
8157
8638
|
copyFileSync,
|
|
8158
8639
|
cpSync as cpSync6,
|
|
8159
|
-
readFileSync as
|
|
8640
|
+
readFileSync as readFileSync10,
|
|
8160
8641
|
writeFileSync as writeFileSync10
|
|
8161
8642
|
} from "fs";
|
|
8162
8643
|
import { resolve as resolve8, basename as basename2, dirname as dirname4 } from "path";
|
|
8163
8644
|
import { homedir as homedir7 } from "os";
|
|
8164
8645
|
|
|
8165
8646
|
// src/plugin/install-sources/local.ts
|
|
8166
|
-
import { existsSync as
|
|
8647
|
+
import { existsSync as existsSync7, cpSync, rmSync } from "fs";
|
|
8167
8648
|
import { resolve as resolve3 } from "path";
|
|
8168
8649
|
import { verifyPlugin, safeCleanup } from "@dyyz1993/xcli-core";
|
|
8169
8650
|
async function installFromLocal(source, name, targetDir) {
|
|
8170
8651
|
const srcPath = resolve3(source);
|
|
8171
|
-
if (!
|
|
8652
|
+
if (!existsSync7(srcPath)) {
|
|
8172
8653
|
throw new Error(`Local path does not exist: ${srcPath}`);
|
|
8173
8654
|
}
|
|
8174
8655
|
const tmpTarget = `${targetDir}-tmp-${Date.now()}`;
|
|
@@ -8181,7 +8662,7 @@ async function installFromLocal(source, name, targetDir) {
|
|
|
8181
8662
|
safeCleanup(tmpTarget);
|
|
8182
8663
|
throw new Error(`Invalid plugin: ${verify.error}`);
|
|
8183
8664
|
}
|
|
8184
|
-
if (
|
|
8665
|
+
if (existsSync7(targetDir)) {
|
|
8185
8666
|
rmSync(targetDir, { recursive: true, force: true });
|
|
8186
8667
|
}
|
|
8187
8668
|
cpSync(tmpTarget, targetDir, { recursive: true, force: true });
|
|
@@ -8201,8 +8682,8 @@ async function installFromLocal(source, name, targetDir) {
|
|
|
8201
8682
|
}
|
|
8202
8683
|
|
|
8203
8684
|
// src/plugin/install-sources/npm.ts
|
|
8204
|
-
import { existsSync as
|
|
8205
|
-
import { resolve as resolve4, join as
|
|
8685
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync6, rmSync as rmSync2, cpSync as cpSync2 } from "fs";
|
|
8686
|
+
import { resolve as resolve4, join as join9 } from "path";
|
|
8206
8687
|
import { tmpdir } from "os";
|
|
8207
8688
|
import {
|
|
8208
8689
|
downloadToFile,
|
|
@@ -8275,13 +8756,13 @@ async function installFromNpm(packageName, name, targetDir) {
|
|
|
8275
8756
|
throw new Error(`No tarball URL for ${packageName}@${latestVersion}`);
|
|
8276
8757
|
}
|
|
8277
8758
|
const tarballUrl = versionMeta.dist.tarball;
|
|
8278
|
-
const tmpDir =
|
|
8759
|
+
const tmpDir = join9(tmpdir(), `xbrowser-npm-${Date.now()}`);
|
|
8279
8760
|
mkdirSync5(tmpDir, { recursive: true });
|
|
8280
8761
|
let warnings = [];
|
|
8281
8762
|
try {
|
|
8282
|
-
const tarballPath =
|
|
8763
|
+
const tarballPath = join9(tmpDir, `${name}.tgz`);
|
|
8283
8764
|
await downloadToFile(tarballUrl, tarballPath);
|
|
8284
|
-
const extractDir =
|
|
8765
|
+
const extractDir = join9(tmpDir, "extracted");
|
|
8285
8766
|
extractTarGz(tarballPath, extractDir);
|
|
8286
8767
|
flattenPackageRoot(extractDir);
|
|
8287
8768
|
const verify = verifyPlugin2(extractDir, { metadataField: "xbrowser" });
|
|
@@ -8289,13 +8770,13 @@ async function installFromNpm(packageName, name, targetDir) {
|
|
|
8289
8770
|
if (!verify.valid) {
|
|
8290
8771
|
throw new Error(`Invalid npm plugin: ${verify.error}`);
|
|
8291
8772
|
}
|
|
8292
|
-
if (
|
|
8773
|
+
if (existsSync8(targetDir)) {
|
|
8293
8774
|
rmSync2(targetDir, { recursive: true, force: true });
|
|
8294
8775
|
}
|
|
8295
8776
|
cpSync2(extractDir, targetDir, { recursive: true, force: true });
|
|
8296
8777
|
const pkgPath = resolve4(targetDir, "package.json");
|
|
8297
|
-
if (
|
|
8298
|
-
const pkg = JSON.parse(
|
|
8778
|
+
if (existsSync8(pkgPath)) {
|
|
8779
|
+
const pkg = JSON.parse(readFileSync6(pkgPath, "utf-8"));
|
|
8299
8780
|
if (!pkg._npmSource) {
|
|
8300
8781
|
pkg._npmSource = { name: packageName, version: latestVersion };
|
|
8301
8782
|
writeFileSync6(pkgPath, JSON.stringify(pkg, null, 2));
|
|
@@ -8315,13 +8796,13 @@ async function installFromNpm(packageName, name, targetDir) {
|
|
|
8315
8796
|
}
|
|
8316
8797
|
|
|
8317
8798
|
// src/plugin/install-sources/git.ts
|
|
8318
|
-
import { existsSync as
|
|
8319
|
-
import { resolve as resolve5, join as
|
|
8799
|
+
import { existsSync as existsSync9, readFileSync as readFileSync7, writeFileSync as writeFileSync7, rmSync as rmSync3, cpSync as cpSync3 } from "fs";
|
|
8800
|
+
import { resolve as resolve5, join as join10 } from "path";
|
|
8320
8801
|
import { tmpdir as tmpdir2 } from "os";
|
|
8321
8802
|
import { execSync as execSync2 } from "child_process";
|
|
8322
8803
|
import { verifyPlugin as verifyPlugin3, safeCleanup as safeCleanup3 } from "@dyyz1993/xcli-core";
|
|
8323
8804
|
async function installFromGit(gitUrl, name, targetDir) {
|
|
8324
|
-
const tmpDir =
|
|
8805
|
+
const tmpDir = join10(tmpdir2(), `xbrowser-git-${Date.now()}`);
|
|
8325
8806
|
let warnings = [];
|
|
8326
8807
|
try {
|
|
8327
8808
|
execSync2(`git clone --depth 1 "${gitUrl}" "${tmpDir}"`, { stdio: "pipe" });
|
|
@@ -8330,14 +8811,14 @@ async function installFromGit(gitUrl, name, targetDir) {
|
|
|
8330
8811
|
if (!verify.valid) {
|
|
8331
8812
|
throw new Error(`Invalid git plugin: ${verify.error}`);
|
|
8332
8813
|
}
|
|
8333
|
-
if (
|
|
8814
|
+
if (existsSync9(targetDir)) {
|
|
8334
8815
|
rmSync3(targetDir, { recursive: true, force: true });
|
|
8335
8816
|
}
|
|
8336
8817
|
cpSync3(tmpDir, targetDir, { recursive: true, force: true });
|
|
8337
8818
|
rmSync3(resolve5(targetDir, ".git"), { recursive: true, force: true });
|
|
8338
8819
|
const pkgPath = resolve5(targetDir, "package.json");
|
|
8339
|
-
if (
|
|
8340
|
-
const pkg = JSON.parse(
|
|
8820
|
+
if (existsSync9(pkgPath)) {
|
|
8821
|
+
const pkg = JSON.parse(readFileSync7(pkgPath, "utf-8"));
|
|
8341
8822
|
if (!pkg._gitSource) {
|
|
8342
8823
|
pkg._gitSource = { url: gitUrl };
|
|
8343
8824
|
writeFileSync7(pkgPath, JSON.stringify(pkg, null, 2));
|
|
@@ -8357,8 +8838,8 @@ async function installFromGit(gitUrl, name, targetDir) {
|
|
|
8357
8838
|
}
|
|
8358
8839
|
|
|
8359
8840
|
// src/plugin/install-sources/url.ts
|
|
8360
|
-
import { existsSync as
|
|
8361
|
-
import { resolve as resolve6, join as
|
|
8841
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync6, readFileSync as readFileSync8, writeFileSync as writeFileSync8, rmSync as rmSync4, cpSync as cpSync4 } from "fs";
|
|
8842
|
+
import { resolve as resolve6, join as join11, basename } from "path";
|
|
8362
8843
|
import { tmpdir as tmpdir3 } from "os";
|
|
8363
8844
|
import {
|
|
8364
8845
|
downloadToFile as downloadToFile2,
|
|
@@ -8368,14 +8849,14 @@ import {
|
|
|
8368
8849
|
safeCleanup as safeCleanup4
|
|
8369
8850
|
} from "@dyyz1993/xcli-core";
|
|
8370
8851
|
async function installFromUrl(url, name, targetDir) {
|
|
8371
|
-
const tmpDir =
|
|
8852
|
+
const tmpDir = join11(tmpdir3(), `xbrowser-url-${Date.now()}`);
|
|
8372
8853
|
mkdirSync6(tmpDir, { recursive: true });
|
|
8373
8854
|
let warnings = [];
|
|
8374
8855
|
try {
|
|
8375
8856
|
const fileName = basename(new URL(url).pathname) || "plugin.tar.gz";
|
|
8376
|
-
const tarballPath =
|
|
8857
|
+
const tarballPath = join11(tmpDir, fileName);
|
|
8377
8858
|
await downloadToFile2(url, tarballPath);
|
|
8378
|
-
const extractDir =
|
|
8859
|
+
const extractDir = join11(tmpDir, "extracted");
|
|
8379
8860
|
extractTarGz2(tarballPath, extractDir);
|
|
8380
8861
|
flattenPackageRoot2(extractDir);
|
|
8381
8862
|
const verify = verifyPlugin4(extractDir, { metadataField: "xbrowser" });
|
|
@@ -8383,13 +8864,13 @@ async function installFromUrl(url, name, targetDir) {
|
|
|
8383
8864
|
if (!verify.valid) {
|
|
8384
8865
|
throw new Error(`Invalid plugin from URL: ${verify.error}`);
|
|
8385
8866
|
}
|
|
8386
|
-
if (
|
|
8867
|
+
if (existsSync10(targetDir)) {
|
|
8387
8868
|
rmSync4(targetDir, { recursive: true, force: true });
|
|
8388
8869
|
}
|
|
8389
8870
|
cpSync4(extractDir, targetDir, { recursive: true, force: true });
|
|
8390
8871
|
const pkgPath = resolve6(targetDir, "package.json");
|
|
8391
|
-
if (
|
|
8392
|
-
const pkg = JSON.parse(
|
|
8872
|
+
if (existsSync10(pkgPath)) {
|
|
8873
|
+
const pkg = JSON.parse(readFileSync8(pkgPath, "utf-8"));
|
|
8393
8874
|
if (!pkg._urlSource) {
|
|
8394
8875
|
pkg._urlSource = { url };
|
|
8395
8876
|
writeFileSync8(pkgPath, JSON.stringify(pkg, null, 2));
|
|
@@ -8410,14 +8891,14 @@ async function installFromUrl(url, name, targetDir) {
|
|
|
8410
8891
|
|
|
8411
8892
|
// src/plugin/install-sources/marketplace.ts
|
|
8412
8893
|
import {
|
|
8413
|
-
existsSync as
|
|
8894
|
+
existsSync as existsSync11,
|
|
8414
8895
|
mkdirSync as mkdirSync7,
|
|
8415
8896
|
writeFileSync as writeFileSync9,
|
|
8416
|
-
readFileSync as
|
|
8897
|
+
readFileSync as readFileSync9,
|
|
8417
8898
|
rmSync as rmSync5,
|
|
8418
8899
|
cpSync as cpSync5
|
|
8419
8900
|
} from "fs";
|
|
8420
|
-
import { resolve as resolve7, join as
|
|
8901
|
+
import { resolve as resolve7, join as join12, dirname as dirname3 } from "path";
|
|
8421
8902
|
import { tmpdir as tmpdir4 } from "os";
|
|
8422
8903
|
import { gunzipSync } from "zlib";
|
|
8423
8904
|
import {
|
|
@@ -8442,11 +8923,11 @@ async function installFromMarketplace(pluginsDir, slug, options) {
|
|
|
8442
8923
|
const plugin = detailData.data;
|
|
8443
8924
|
const name = options?.name || String(plugin.slug || slug);
|
|
8444
8925
|
const targetDir = resolve7(pluginsDir, name);
|
|
8445
|
-
if (
|
|
8926
|
+
if (existsSync11(targetDir) && !options?.force) {
|
|
8446
8927
|
throw new Error(`Plugin "${name}" already exists. Use --force to overwrite.`);
|
|
8447
8928
|
}
|
|
8448
8929
|
mkdirSync7(targetDir, { recursive: true });
|
|
8449
|
-
const tmpDir =
|
|
8930
|
+
const tmpDir = join12(tmpdir4(), `xbrowser-marketplace-${Date.now()}`);
|
|
8450
8931
|
mkdirSync7(tmpDir, { recursive: true });
|
|
8451
8932
|
const realSlug = String(plugin.slug || slug);
|
|
8452
8933
|
try {
|
|
@@ -8477,7 +8958,7 @@ function isManifestArray(data) {
|
|
|
8477
8958
|
return Array.isArray(data) && data.length > 0 && typeof data[0].path === "string" && typeof data[0].content === "string";
|
|
8478
8959
|
}
|
|
8479
8960
|
function extractManifestToDir(manifest, targetDir) {
|
|
8480
|
-
if (
|
|
8961
|
+
if (existsSync11(targetDir)) {
|
|
8481
8962
|
rmSync5(targetDir, { recursive: true, force: true });
|
|
8482
8963
|
}
|
|
8483
8964
|
mkdirSync7(targetDir, { recursive: true });
|
|
@@ -8509,18 +8990,18 @@ async function downloadAndExtractMarketplaceTarball(baseUrl, slug, tmpDir, targe
|
|
|
8509
8990
|
}
|
|
8510
8991
|
if (tarballRes.status === 302 || tarballRes.headers.get("location")) {
|
|
8511
8992
|
const redirectUrl = tarballRes.headers.get("location");
|
|
8512
|
-
const tarballPath =
|
|
8993
|
+
const tarballPath = join12(tmpDir, `${slug}.tar.gz`);
|
|
8513
8994
|
await downloadToFile3(redirectUrl, tarballPath);
|
|
8514
|
-
const buffer =
|
|
8995
|
+
const buffer = readFileSync9(tarballPath);
|
|
8515
8996
|
const manifest = tryParseAsGzippedManifest(buffer);
|
|
8516
8997
|
if (manifest) {
|
|
8517
8998
|
extractManifestToDir(manifest, targetDir);
|
|
8518
8999
|
return;
|
|
8519
9000
|
}
|
|
8520
|
-
const extractDir =
|
|
9001
|
+
const extractDir = join12(tmpDir, "extracted");
|
|
8521
9002
|
extractTarGz3(tarballPath, extractDir);
|
|
8522
9003
|
flattenPackageRoot3(extractDir);
|
|
8523
|
-
if (
|
|
9004
|
+
if (existsSync11(targetDir)) {
|
|
8524
9005
|
rmSync5(targetDir, { recursive: true, force: true });
|
|
8525
9006
|
}
|
|
8526
9007
|
cpSync5(extractDir, targetDir, { recursive: true, force: true });
|
|
@@ -8531,13 +9012,13 @@ async function downloadAndExtractMarketplaceTarball(baseUrl, slug, tmpDir, targe
|
|
|
8531
9012
|
extractManifestToDir(manifest, targetDir);
|
|
8532
9013
|
return;
|
|
8533
9014
|
}
|
|
8534
|
-
const tarballPath =
|
|
9015
|
+
const tarballPath = join12(tmpDir, `${slug}.tar.gz`);
|
|
8535
9016
|
writeFileSync9(tarballPath, buffer);
|
|
8536
9017
|
try {
|
|
8537
|
-
const extractDir =
|
|
9018
|
+
const extractDir = join12(tmpDir, "extracted");
|
|
8538
9019
|
extractTarGz3(tarballPath, extractDir);
|
|
8539
9020
|
flattenPackageRoot3(extractDir);
|
|
8540
|
-
if (
|
|
9021
|
+
if (existsSync11(targetDir)) {
|
|
8541
9022
|
rmSync5(targetDir, { recursive: true, force: true });
|
|
8542
9023
|
}
|
|
8543
9024
|
cpSync5(extractDir, targetDir, { recursive: true, force: true });
|
|
@@ -8573,11 +9054,11 @@ function writeMarketplacePackageJson(plugin, slug, name, baseUrl, targetDir) {
|
|
|
8573
9054
|
}
|
|
8574
9055
|
};
|
|
8575
9056
|
const pkgPath = resolve7(targetDir, "package.json");
|
|
8576
|
-
if (!
|
|
9057
|
+
if (!existsSync11(pkgPath)) {
|
|
8577
9058
|
writeFileSync9(pkgPath, JSON.stringify(packageJson, null, 2));
|
|
8578
9059
|
} else {
|
|
8579
9060
|
try {
|
|
8580
|
-
const existing = JSON.parse(
|
|
9061
|
+
const existing = JSON.parse(readFileSync9(pkgPath, "utf-8"));
|
|
8581
9062
|
const merged = {
|
|
8582
9063
|
...existing,
|
|
8583
9064
|
xbrowser: { ...existing.xbrowser, ...packageJson.xbrowser },
|
|
@@ -8590,7 +9071,7 @@ function writeMarketplacePackageJson(plugin, slug, name, baseUrl, targetDir) {
|
|
|
8590
9071
|
}
|
|
8591
9072
|
}
|
|
8592
9073
|
function ensureIndexFile(plugin, name, targetDir) {
|
|
8593
|
-
if (!
|
|
9074
|
+
if (!existsSync11(resolve7(targetDir, "index.ts")) && !existsSync11(resolve7(targetDir, "index.js"))) {
|
|
8594
9075
|
const commands = plugin.commands || [];
|
|
8595
9076
|
const commandHandlers = commands.length > 0 ? commands.map((cmd) => {
|
|
8596
9077
|
return [
|
|
@@ -8644,7 +9125,7 @@ var PluginInstaller = class {
|
|
|
8644
9125
|
const type = this.detectSourceType(source);
|
|
8645
9126
|
const name = options?.name || this.deriveName(source, type);
|
|
8646
9127
|
const targetDir = resolve8(this.pluginsDir, name);
|
|
8647
|
-
if (
|
|
9128
|
+
if (existsSync12(targetDir) && !options?.force) {
|
|
8648
9129
|
throw new Error(`Plugin "${name}" already exists. Use --force to overwrite.`);
|
|
8649
9130
|
}
|
|
8650
9131
|
mkdirSync8(targetDir, { recursive: true });
|
|
@@ -8687,10 +9168,10 @@ var PluginInstaller = class {
|
|
|
8687
9168
|
*/
|
|
8688
9169
|
async fixSharedDeps(pluginDir) {
|
|
8689
9170
|
const indexPath = resolve8(pluginDir, "index.ts");
|
|
8690
|
-
if (!
|
|
9171
|
+
if (!existsSync12(indexPath)) return;
|
|
8691
9172
|
let content;
|
|
8692
9173
|
try {
|
|
8693
|
-
content =
|
|
9174
|
+
content = readFileSync10(indexPath, "utf8");
|
|
8694
9175
|
} catch {
|
|
8695
9176
|
return;
|
|
8696
9177
|
}
|
|
@@ -8705,7 +9186,7 @@ var PluginInstaller = class {
|
|
|
8705
9186
|
const toCopy = [];
|
|
8706
9187
|
for (const file of missingFiles) {
|
|
8707
9188
|
const targetPath = resolve8(sharedDir, file);
|
|
8708
|
-
if (!
|
|
9189
|
+
if (!existsSync12(targetPath)) {
|
|
8709
9190
|
toCopy.push(file);
|
|
8710
9191
|
}
|
|
8711
9192
|
}
|
|
@@ -8716,7 +9197,7 @@ var PluginInstaller = class {
|
|
|
8716
9197
|
];
|
|
8717
9198
|
let sourceSharedDir = null;
|
|
8718
9199
|
for (const dir of repoSharedDirs) {
|
|
8719
|
-
if (
|
|
9200
|
+
if (existsSync12(dir)) {
|
|
8720
9201
|
sourceSharedDir = dir;
|
|
8721
9202
|
break;
|
|
8722
9203
|
}
|
|
@@ -8733,7 +9214,7 @@ var PluginInstaller = class {
|
|
|
8733
9214
|
const content2 = await resp.text();
|
|
8734
9215
|
const dst = resolve8(sharedDir, file);
|
|
8735
9216
|
const dstDir = dirname4(dst);
|
|
8736
|
-
if (!
|
|
9217
|
+
if (!existsSync12(dstDir)) mkdirSync8(dstDir, { recursive: true });
|
|
8737
9218
|
writeFileSync10(dst, content2, "utf-8");
|
|
8738
9219
|
console.log(`\u2705 Downloaded shared/${file} from GitHub`);
|
|
8739
9220
|
} else {
|
|
@@ -8755,7 +9236,7 @@ var PluginInstaller = class {
|
|
|
8755
9236
|
for (const file of toCopy) {
|
|
8756
9237
|
const src = resolve8(sourceSharedDir, file);
|
|
8757
9238
|
const dst = resolve8(sharedDir, file);
|
|
8758
|
-
if (
|
|
9239
|
+
if (existsSync12(src)) {
|
|
8759
9240
|
try {
|
|
8760
9241
|
cpSync6(dirname4(src), dirname4(dst), { recursive: true });
|
|
8761
9242
|
console.log(`\u2705 Copied shared/${file} for plugin "${basename2(pluginDir)}"`);
|
|
@@ -8805,7 +9286,7 @@ var PluginInstaller = class {
|
|
|
8805
9286
|
*/
|
|
8806
9287
|
async uninstall(name) {
|
|
8807
9288
|
const targetDir = resolve8(this.pluginsDir, name);
|
|
8808
|
-
if (!
|
|
9289
|
+
if (!existsSync12(targetDir)) {
|
|
8809
9290
|
throw new Error(`Plugin "${name}" not found`);
|
|
8810
9291
|
}
|
|
8811
9292
|
rmSync6(targetDir, { recursive: true, force: true });
|
|
@@ -8816,7 +9297,7 @@ var PluginInstaller = class {
|
|
|
8816
9297
|
* @returns Array of installed plugin information.
|
|
8817
9298
|
*/
|
|
8818
9299
|
async list(_options) {
|
|
8819
|
-
if (!
|
|
9300
|
+
if (!existsSync12(this.pluginsDir)) return [];
|
|
8820
9301
|
const entries = readdirSync2(this.pluginsDir, { withFileTypes: true });
|
|
8821
9302
|
const plugins = [];
|
|
8822
9303
|
for (const entry of entries) {
|
|
@@ -8824,7 +9305,7 @@ var PluginInstaller = class {
|
|
|
8824
9305
|
const pluginPath = resolve8(this.pluginsDir, entry.name);
|
|
8825
9306
|
const indexPath = resolve8(pluginPath, "index.ts");
|
|
8826
9307
|
const indexJsPath = resolve8(pluginPath, "index.js");
|
|
8827
|
-
if (!
|
|
9308
|
+
if (!existsSync12(indexPath) && !existsSync12(indexJsPath)) continue;
|
|
8828
9309
|
const metadata = PluginMetadataParser.parseFromPackageJson(pluginPath);
|
|
8829
9310
|
let source = "local";
|
|
8830
9311
|
const pkg = readJsonFile(resolve8(pluginPath, "package.json"), {});
|
|
@@ -8851,10 +9332,10 @@ var PluginInstaller = class {
|
|
|
8851
9332
|
}
|
|
8852
9333
|
if (source.startsWith("file://")) {
|
|
8853
9334
|
const filePath = decodeURIComponent(new URL(source).pathname);
|
|
8854
|
-
if (
|
|
9335
|
+
if (existsSync12(filePath)) return "url";
|
|
8855
9336
|
}
|
|
8856
9337
|
if (source.endsWith(".git") || source.includes("github.com/")) return "git";
|
|
8857
|
-
if (
|
|
9338
|
+
if (existsSync12(resolve8(source))) return "local";
|
|
8858
9339
|
return "npm";
|
|
8859
9340
|
}
|
|
8860
9341
|
deriveName(source, type) {
|
|
@@ -9783,7 +10264,7 @@ var previewBuiltin = {
|
|
|
9783
10264
|
|
|
9784
10265
|
// src/builtins/knowledge.ts
|
|
9785
10266
|
init_site_knowledge();
|
|
9786
|
-
import { existsSync as
|
|
10267
|
+
import { existsSync as existsSync14 } from "fs";
|
|
9787
10268
|
var knowledgeBuiltin = {
|
|
9788
10269
|
name: "knowledge",
|
|
9789
10270
|
description: "View LLM-readable site knowledge base (selectors, forms, APIs)",
|
|
@@ -9962,8 +10443,8 @@ var knowledgeBuiltin = {
|
|
|
9962
10443
|
}
|
|
9963
10444
|
const mdPath = getKnowledgePath(domain, "md");
|
|
9964
10445
|
const jsonPath = getKnowledgePath(domain, "json");
|
|
9965
|
-
console.log(`Markdown: ${mdPath} (${
|
|
9966
|
-
console.log(`JSON: ${jsonPath} (${
|
|
10446
|
+
console.log(`Markdown: ${mdPath} (${existsSync14(mdPath) ? "exists" : "not found"})`);
|
|
10447
|
+
console.log(`JSON: ${jsonPath} (${existsSync14(jsonPath) ? "exists" : "not found"})`);
|
|
9967
10448
|
return;
|
|
9968
10449
|
}
|
|
9969
10450
|
console.error(`Unknown subcommand: ${subcommand}`);
|
|
@@ -10065,10 +10546,12 @@ var HTML_TAGS = /* @__PURE__ */ new Set([
|
|
|
10065
10546
|
"map",
|
|
10066
10547
|
"area"
|
|
10067
10548
|
]);
|
|
10549
|
+
var SELECTOR_DSL_PREFIXES = /^(text=|popup-text=|xpath=|role=|css=|chain=)/;
|
|
10068
10550
|
function normalizeSelector(input) {
|
|
10069
10551
|
if (!input) return input;
|
|
10070
10552
|
if (/^[#\.\[\:\/]/.test(input)) return input;
|
|
10071
10553
|
if (input === "*") return input;
|
|
10554
|
+
if (SELECTOR_DSL_PREFIXES.test(input)) return input;
|
|
10072
10555
|
if (/[>\s+,~]/.test(input)) return input;
|
|
10073
10556
|
const tagName = input.match(/^[a-zA-Z][a-zA-Z0-9]*/)?.[0];
|
|
10074
10557
|
if (tagName && HTML_TAGS.has(tagName.toLowerCase())) return input;
|
|
@@ -10243,11 +10726,29 @@ async function handleBrowserCommand(command, args, options, sessionName, mode, c
|
|
|
10243
10726
|
output: options.output || options.o || args[0]
|
|
10244
10727
|
};
|
|
10245
10728
|
break;
|
|
10246
|
-
case "
|
|
10729
|
+
case "ua": {
|
|
10730
|
+
cmdName = "ua";
|
|
10731
|
+
const val = args.join(" ").trim() || options.value || "";
|
|
10732
|
+
params = { value: val };
|
|
10733
|
+
break;
|
|
10734
|
+
}
|
|
10735
|
+
case "eval": {
|
|
10247
10736
|
cmdName = "eval";
|
|
10248
|
-
|
|
10249
|
-
|
|
10737
|
+
const frameVal = options.frame || void 0;
|
|
10738
|
+
const exprParts = [];
|
|
10739
|
+
for (let i = 0; i < args.length; i++) {
|
|
10740
|
+
if (args[i] === "--frame") {
|
|
10741
|
+
i++;
|
|
10742
|
+
continue;
|
|
10743
|
+
}
|
|
10744
|
+
if (args[i].startsWith("--frame=")) continue;
|
|
10745
|
+
exprParts.push(args[i]);
|
|
10746
|
+
}
|
|
10747
|
+
params = { expression: exprParts.join(" ") || options.expression || "" };
|
|
10748
|
+
if (frameVal) params.frame = frameVal;
|
|
10749
|
+
if (!params.expression) outputError("Usage: xbrowser eval <expression> [--frame <url-substring>]");
|
|
10250
10750
|
break;
|
|
10751
|
+
}
|
|
10251
10752
|
case "scroll": {
|
|
10252
10753
|
let direction = "down";
|
|
10253
10754
|
let distance;
|
|
@@ -10539,14 +11040,14 @@ async function handleBrowserCommand(command, args, options, sessionName, mode, c
|
|
|
10539
11040
|
|
|
10540
11041
|
// src/cli/session-routes.ts
|
|
10541
11042
|
import { homedir as homedir8 } from "os";
|
|
10542
|
-
import { join as
|
|
11043
|
+
import { join as join14 } from "path";
|
|
10543
11044
|
import { readdirSync as readdirSync3, rmSync as rmSync7 } from "fs";
|
|
10544
11045
|
function cleanSessionFiles() {
|
|
10545
|
-
const dir =
|
|
11046
|
+
const dir = join14(homedir8(), ".xbrowser", "sessions");
|
|
10546
11047
|
let count = 0;
|
|
10547
11048
|
try {
|
|
10548
11049
|
for (const entry of readdirSync3(dir, { withFileTypes: true })) {
|
|
10549
|
-
const p =
|
|
11050
|
+
const p = join14(dir, entry.name);
|
|
10550
11051
|
rmSync7(p, { recursive: true, force: true });
|
|
10551
11052
|
count++;
|
|
10552
11053
|
}
|
|
@@ -10950,7 +11451,7 @@ async function handlePlugin(args, options, mode) {
|
|
|
10950
11451
|
} catch {
|
|
10951
11452
|
}
|
|
10952
11453
|
try {
|
|
10953
|
-
const { daemonPing } = await import("./daemon-client-
|
|
11454
|
+
const { daemonPing } = await import("./daemon-client-HEGAXWGK.js");
|
|
10954
11455
|
if (await daemonPing()) {
|
|
10955
11456
|
await fetch("http://localhost:9224/rpc", {
|
|
10956
11457
|
method: "POST",
|
|
@@ -11100,7 +11601,7 @@ function handleDaemon(args, options, mode) {
|
|
|
11100
11601
|
}
|
|
11101
11602
|
|
|
11102
11603
|
// src/cli/record-routes.ts
|
|
11103
|
-
import { existsSync as
|
|
11604
|
+
import { existsSync as existsSync15 } from "fs";
|
|
11104
11605
|
async function handleRecord(args, options, mode) {
|
|
11105
11606
|
const sub = args[0];
|
|
11106
11607
|
switch (sub) {
|
|
@@ -11131,9 +11632,9 @@ async function handleRecord(args, options, mode) {
|
|
|
11131
11632
|
outputError(String(result.error || "Failed to stop recording"));
|
|
11132
11633
|
return;
|
|
11133
11634
|
}
|
|
11134
|
-
if (output && !
|
|
11635
|
+
if (output && !existsSync15(output)) {
|
|
11135
11636
|
const defaultRecording = SessionRecorder.getRecordingsDir(sessionName) + "/recording.json";
|
|
11136
|
-
if (
|
|
11637
|
+
if (existsSync15(defaultRecording)) {
|
|
11137
11638
|
try {
|
|
11138
11639
|
const { mkdirSync: mkdirSync11, copyFileSync: copyFileSync2 } = await import("fs");
|
|
11139
11640
|
const { dirname: pathDirname } = await import("path");
|
|
@@ -11516,10 +12017,10 @@ async function handleFilter(args, _mode, options) {
|
|
|
11516
12017
|
}
|
|
11517
12018
|
}
|
|
11518
12019
|
async function handleGeneratePlugin(sessionName, pluginName, outputDir) {
|
|
11519
|
-
const { SessionRecorder: SessionRecorder2 } = await import("./session-recorder-
|
|
12020
|
+
const { SessionRecorder: SessionRecorder2 } = await import("./session-recorder-SLDBENVF.js");
|
|
11520
12021
|
const { readSiteKnowledge: readSiteKnowledge2, toMarkdown } = await import("./site-knowledge-SYC6VCDB.js");
|
|
11521
12022
|
const { mkdirSync: mkdirSync11, writeFileSync: writeFileSync13 } = await import("fs");
|
|
11522
|
-
const { join:
|
|
12023
|
+
const { join: join16 } = await import("path");
|
|
11523
12024
|
const data = SessionRecorder2.readData(sessionName);
|
|
11524
12025
|
if (!data) {
|
|
11525
12026
|
outputError(`No recording found for session "${sessionName}". Run \`xbrowser record stop --session ${sessionName}\` first.`);
|
|
@@ -11531,14 +12032,14 @@ async function handleGeneratePlugin(sessionName, pluginName, outputDir) {
|
|
|
11531
12032
|
} catch {
|
|
11532
12033
|
}
|
|
11533
12034
|
const finalPluginName = pluginName || domain.split(".")[0] || "my-site";
|
|
11534
|
-
const finalOutputDir = outputDir ||
|
|
12035
|
+
const finalOutputDir = outputDir || join16(process.cwd(), ".xcli", "plugins", finalPluginName);
|
|
11535
12036
|
const knowledge = readSiteKnowledge2(domain);
|
|
11536
12037
|
const knowledgeMd = knowledge ? toMarkdown(knowledge) : "";
|
|
11537
12038
|
const pluginCode = generatePluginCode(finalPluginName, domain, data, knowledgeMd);
|
|
11538
|
-
mkdirSync11(
|
|
11539
|
-
writeFileSync13(
|
|
12039
|
+
mkdirSync11(join16(finalOutputDir), { recursive: true });
|
|
12040
|
+
writeFileSync13(join16(finalOutputDir, "index.ts"), pluginCode, "utf-8");
|
|
11540
12041
|
if (knowledgeMd) {
|
|
11541
|
-
writeFileSync13(
|
|
12042
|
+
writeFileSync13(join16(finalOutputDir, "SITE_KNOWLEDGE.md"), knowledgeMd, "utf-8");
|
|
11542
12043
|
}
|
|
11543
12044
|
console.log("");
|
|
11544
12045
|
console.log("=== Plugin Generated ===");
|
|
@@ -12081,7 +12582,7 @@ async function handleNetCommand(args, options, mode, sessionName) {
|
|
|
12081
12582
|
|
|
12082
12583
|
// src/cli/test-routes.ts
|
|
12083
12584
|
import { execSync as execSync3 } from "child_process";
|
|
12084
|
-
import { readFileSync as
|
|
12585
|
+
import { readFileSync as readFileSync11 } from "fs";
|
|
12085
12586
|
import { resolve as resolve9 } from "path";
|
|
12086
12587
|
function findPluginPath(plugin) {
|
|
12087
12588
|
const candidates = [
|
|
@@ -12090,7 +12591,7 @@ function findPluginPath(plugin) {
|
|
|
12090
12591
|
];
|
|
12091
12592
|
for (const p of candidates) {
|
|
12092
12593
|
try {
|
|
12093
|
-
|
|
12594
|
+
readFileSync11(p, "utf-8");
|
|
12094
12595
|
return p;
|
|
12095
12596
|
} catch {
|
|
12096
12597
|
}
|
|
@@ -12101,7 +12602,7 @@ function extractSchema(plugin, command) {
|
|
|
12101
12602
|
const pluginPath = findPluginPath(plugin);
|
|
12102
12603
|
let src;
|
|
12103
12604
|
try {
|
|
12104
|
-
src =
|
|
12605
|
+
src = readFileSync11(pluginPath, "utf-8");
|
|
12105
12606
|
} catch {
|
|
12106
12607
|
return null;
|
|
12107
12608
|
}
|
|
@@ -12822,6 +13323,9 @@ var HTTPServer = class {
|
|
|
12822
13323
|
this.server = null;
|
|
12823
13324
|
reject(err);
|
|
12824
13325
|
});
|
|
13326
|
+
server.headersTimeout = 20 * 6e4;
|
|
13327
|
+
server.requestTimeout = 20 * 6e4;
|
|
13328
|
+
server.keepAliveTimeout = 20 * 6e4;
|
|
12825
13329
|
server.listen(this.port, this.host, () => {
|
|
12826
13330
|
const addr = server.address();
|
|
12827
13331
|
if (addr && typeof addr === "object") {
|
|
@@ -13021,7 +13525,7 @@ function extractSessionNameFromArgv(argv) {
|
|
|
13021
13525
|
async function handleEvalMode(argv) {
|
|
13022
13526
|
const evalCommands = parseEvalFlags(argv);
|
|
13023
13527
|
if (evalCommands.length === 0) return;
|
|
13024
|
-
const chain = evalCommands.join(" ; ");
|
|
13528
|
+
const chain = evalCommands.map((script) => `eval --script-b64 ${Buffer.from(script, "utf8").toString("base64url")}`).join(" ; ");
|
|
13025
13529
|
const cdpEndpoint = extractCdpFromArgv(argv);
|
|
13026
13530
|
const sessionArgIdx = argv.indexOf("--session");
|
|
13027
13531
|
const sessionName = sessionArgIdx >= 0 && argv[sessionArgIdx + 1] ? argv[sessionArgIdx + 1] : process.env.XBROWSER_SESSION || "default";
|
|
@@ -13029,6 +13533,51 @@ async function handleEvalMode(argv) {
|
|
|
13029
13533
|
printChainResult(chainResult);
|
|
13030
13534
|
if (!chainResult.success) throw new Error("Command failed");
|
|
13031
13535
|
}
|
|
13536
|
+
function findEvalStart(argv) {
|
|
13537
|
+
const flagsWithValue = /* @__PURE__ */ new Set(["--session", "--cdp"]);
|
|
13538
|
+
for (let i = 0; i < argv.length; i++) {
|
|
13539
|
+
const a = argv[i];
|
|
13540
|
+
if (a === "-e" || a === "--eval") return -1;
|
|
13541
|
+
if (a.startsWith("--")) {
|
|
13542
|
+
if (flagsWithValue.has(a)) {
|
|
13543
|
+
i++;
|
|
13544
|
+
continue;
|
|
13545
|
+
}
|
|
13546
|
+
continue;
|
|
13547
|
+
}
|
|
13548
|
+
return a === "eval" ? i : -1;
|
|
13549
|
+
}
|
|
13550
|
+
return -1;
|
|
13551
|
+
}
|
|
13552
|
+
async function handleEvalScript(parts, argv) {
|
|
13553
|
+
const cdpEndpoint = extractCdpFromArgv(argv);
|
|
13554
|
+
const sessionName = extractSessionNameFromArgv(argv);
|
|
13555
|
+
let frame;
|
|
13556
|
+
const scriptLines = [];
|
|
13557
|
+
for (let i = 0; i < parts.length; i++) {
|
|
13558
|
+
const part = parts[i];
|
|
13559
|
+
if (part === "--frame" && parts[i + 1]) {
|
|
13560
|
+
frame = parts[i + 1];
|
|
13561
|
+
i++;
|
|
13562
|
+
continue;
|
|
13563
|
+
}
|
|
13564
|
+
if (part === "--session" || part === "--cdp") {
|
|
13565
|
+
i++;
|
|
13566
|
+
continue;
|
|
13567
|
+
}
|
|
13568
|
+
scriptLines.push(part);
|
|
13569
|
+
}
|
|
13570
|
+
const script = scriptLines.join("\n");
|
|
13571
|
+
if (!script.trim()) {
|
|
13572
|
+
console.error("Error: eval requires a script (usage: xbrowser eval <js...>)");
|
|
13573
|
+
throw new Error("Command failed");
|
|
13574
|
+
}
|
|
13575
|
+
const b64 = Buffer.from(script, "utf8").toString("base64url");
|
|
13576
|
+
const chain = frame ? `eval --frame ${frame} --script-b64 ${b64}` : `eval --script-b64 ${b64}`;
|
|
13577
|
+
const chainResult = await executeChain(chain, { cdpEndpoint, sessionName });
|
|
13578
|
+
printChainResult(chainResult);
|
|
13579
|
+
if (!chainResult.success) throw new Error("Command failed");
|
|
13580
|
+
}
|
|
13032
13581
|
async function handleChainInput(input, argv) {
|
|
13033
13582
|
const cdpEndpoint = argv ? extractCdpFromArgv(argv) : void 0;
|
|
13034
13583
|
const hasJson = argv ? argv.some((a) => a === "--json" || a.startsWith("--json=") || a.includes(" --json") || a.startsWith("--json")) || argv.includes("-j") : false;
|
|
@@ -13066,6 +13615,11 @@ async function routeCommand(argvIn, stdinCommands) {
|
|
|
13066
13615
|
await handleStdinMode(stdinCommands, argv);
|
|
13067
13616
|
return;
|
|
13068
13617
|
}
|
|
13618
|
+
const evalStart = findEvalStart(argv);
|
|
13619
|
+
if (evalStart >= 0 && argv.length > evalStart + 1) {
|
|
13620
|
+
await handleEvalScript(argv.slice(evalStart + 1), argv);
|
|
13621
|
+
return;
|
|
13622
|
+
}
|
|
13069
13623
|
if (parseEvalFlags(argv).length > 0) {
|
|
13070
13624
|
await handleEvalMode(argv);
|
|
13071
13625
|
return;
|
|
@@ -13457,7 +14011,7 @@ Run "xbrowser ${command} ${subCommand} --help" to see available parameters.`
|
|
|
13457
14011
|
}
|
|
13458
14012
|
const needsBrowser = cmdEntry.scope === "page" || cmdEntry.scope === "browser";
|
|
13459
14013
|
if (needsBrowser && !process.env.XBROWSER_DAEMON_WORKER) {
|
|
13460
|
-
const { forwardPluginExec } = await import("./daemon-client-
|
|
14014
|
+
const { forwardPluginExec } = await import("./daemon-client-HEGAXWGK.js");
|
|
13461
14015
|
const userTimeout = typeof params.timeout === "number" && params.timeout > 0 ? params.timeout * 1e3 + 3e4 : void 0;
|
|
13462
14016
|
const result = await forwardPluginExec(`${command}.${subCommand}`, params, sessionName, cdpEndpoint, userTimeout);
|
|
13463
14017
|
const resultData = result && typeof result === "object" && "data" in result ? result.data : void 0;
|
|
@@ -13497,7 +14051,7 @@ Run "xbrowser ${command} ${subCommand} --help" to see available parameters.`
|
|
|
13497
14051
|
const targetPage = pages[cmdTabIndex];
|
|
13498
14052
|
await targetPage.bringToFront().catch(() => {
|
|
13499
14053
|
});
|
|
13500
|
-
const { setActivePage: setActivePage2 } = await import("./browser-
|
|
14054
|
+
const { setActivePage: setActivePage2 } = await import("./browser-3HQKDZQ6.js");
|
|
13501
14055
|
setActivePage2(session, targetPage);
|
|
13502
14056
|
}
|
|
13503
14057
|
}
|
|
@@ -15070,12 +15624,12 @@ var FileListHandler = class {
|
|
|
15070
15624
|
const msg = ctx.message;
|
|
15071
15625
|
try {
|
|
15072
15626
|
const { readdirSync: readdirSync4, statSync } = await import("fs");
|
|
15073
|
-
const { join:
|
|
15627
|
+
const { join: join16, resolve: resolve10 } = await import("path");
|
|
15074
15628
|
const targetPath = resolve10(msg.path);
|
|
15075
15629
|
const entries = readdirSync4(targetPath);
|
|
15076
15630
|
const files = entries.map((name) => {
|
|
15077
15631
|
try {
|
|
15078
|
-
const stat = statSync(
|
|
15632
|
+
const stat = statSync(join16(targetPath, name));
|
|
15079
15633
|
return { name, isDir: stat.isDirectory(), size: stat.size, modified: stat.mtime.toISOString() };
|
|
15080
15634
|
} catch {
|
|
15081
15635
|
return { name, isDir: false, size: 0, modified: "" };
|
|
@@ -15092,10 +15646,10 @@ var FileDownloadHandler = class {
|
|
|
15092
15646
|
async handle(ctx) {
|
|
15093
15647
|
const msg = ctx.message;
|
|
15094
15648
|
try {
|
|
15095
|
-
const { readFileSync:
|
|
15649
|
+
const { readFileSync: readFileSync13 } = await import("fs");
|
|
15096
15650
|
const { resolve: resolve10, basename: basename3 } = await import("path");
|
|
15097
15651
|
const targetPath = resolve10(msg.path);
|
|
15098
|
-
const data =
|
|
15652
|
+
const data = readFileSync13(targetPath);
|
|
15099
15653
|
const base64 = data.toString("base64");
|
|
15100
15654
|
const ext = targetPath.split(".").pop()?.toLowerCase() || "";
|
|
15101
15655
|
const mimeMap = {
|
|
@@ -16670,7 +17224,7 @@ var DataCollector = class {
|
|
|
16670
17224
|
return results;
|
|
16671
17225
|
}
|
|
16672
17226
|
async createBrowserContext() {
|
|
16673
|
-
const { launch } = await import("./cdp-driver-
|
|
17227
|
+
const { launch } = await import("./cdp-driver-YIKR4BUX.js");
|
|
16674
17228
|
const { browser } = await launch({
|
|
16675
17229
|
headless: true,
|
|
16676
17230
|
args: ["--no-sandbox", "--disable-setuid-sandbox"]
|
|
@@ -16710,7 +17264,7 @@ var DataCollector = class {
|
|
|
16710
17264
|
|
|
16711
17265
|
// src/cdp-interceptor/index.ts
|
|
16712
17266
|
async function createCDPInterceptor(config) {
|
|
16713
|
-
const { CDPInterceptorProxy: CDPInterceptorProxy2 } = await import("./proxy-
|
|
17267
|
+
const { CDPInterceptorProxy: CDPInterceptorProxy2 } = await import("./proxy-LUR4U5YF.js");
|
|
16714
17268
|
const proxy = new CDPInterceptorProxy2(config);
|
|
16715
17269
|
await proxy.start();
|
|
16716
17270
|
return proxy;
|