@xbrowser/cli 1.11.0 → 1.13.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/{browser-XNU7JQYC.js → browser-AQ2AT2YZ.js} +2 -2
- package/dist/{browser-Q5APBNF6.js → browser-OMGJMSBB.js} +1 -1
- package/dist/{browser-V3JIWSTR.js → browser-T7OVLAEC.js} +4 -3
- package/dist/{cdp-driver-XFTTZI5O.js → cdp-driver-ALDNOZYD.js} +1176 -13
- package/dist/{cdp-driver-GD6YBBGE.js → cdp-driver-HYCLE4YP.js} +2 -1
- package/dist/{cdp-driver-VEK6BNN6.js → cdp-driver-VS7Z7W7Y.js} +1 -1
- package/dist/chunk-A6LPGFAL.js +437 -0
- package/dist/{chunk-JEDP4PJW.js → chunk-BY5TT6WZ.js} +912 -688
- package/dist/{chunk-XKQVUFUS.js → chunk-CW7L53JE.js} +2 -2
- package/dist/{chunk-ABXMBNQ6.js → chunk-H2A5JUK5.js} +37 -466
- package/dist/{chunk-BNO7OKO4.js → chunk-HBMEFSTB.js} +39 -0
- package/dist/chunk-HD4FX2N5.js +1255 -0
- package/dist/{chunk-DWDXEGVK.js → chunk-NKW4A74J.js} +9 -3
- package/dist/{chunk-SQHSZENE.js → chunk-OMU63E6J.js} +3 -22
- package/dist/{chunk-7OFM755Z.js → chunk-R2POF2GP.js} +236 -13
- package/dist/{chunk-WHPBNUKB.js → chunk-SH6OTPXN.js} +42 -46
- package/dist/{chunk-XQ4HRPDJ.js → chunk-T2CNKWPV.js} +1177 -13
- package/dist/{chunk-MWFHZUIY.js → chunk-TEXCXIBW.js} +1 -1
- package/dist/{chunk-IIM5GOD7.js → chunk-ZTHE5RBZ.js} +7 -1
- package/dist/cli.js +1075 -569
- package/dist/{daemon-client-MMDRYCF5.js → daemon-client-7D6EZNOE.js} +42 -46
- package/dist/{daemon-client-Y2YSZCGK.js → daemon-client-HEGAXWGK.js} +1 -1
- package/dist/daemon-main.js +950 -499
- 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 +2 -0
- package/dist/index.js +1086 -578
- 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-replayer-WIUYVN5J.js → session-replayer-UVH5FGY2.js} +1 -1
- package/package.json +1 -1
- package/dist/chunk-HVPUVVSA.js +0 -2194
package/dist/daemon-main.js
CHANGED
|
@@ -20,7 +20,6 @@ import {
|
|
|
20
20
|
closeEphemeralContext,
|
|
21
21
|
closeSessionByName,
|
|
22
22
|
createEphemeralContext,
|
|
23
|
-
createRuleEngine,
|
|
24
23
|
createSession,
|
|
25
24
|
findOrRestoreSession,
|
|
26
25
|
findSession,
|
|
@@ -31,8 +30,10 @@ import {
|
|
|
31
30
|
resolveLaunchOpts,
|
|
32
31
|
saveSessionDiskMeta,
|
|
33
32
|
setActivePage
|
|
34
|
-
} from "./chunk-
|
|
35
|
-
import
|
|
33
|
+
} from "./chunk-HD4FX2N5.js";
|
|
34
|
+
import {
|
|
35
|
+
createRuleEngine
|
|
36
|
+
} from "./chunk-T2CNKWPV.js";
|
|
36
37
|
import {
|
|
37
38
|
queryJS
|
|
38
39
|
} from "./chunk-3FWLW7FS.js";
|
|
@@ -45,18 +46,19 @@ import {
|
|
|
45
46
|
formatDetectionMessage
|
|
46
47
|
} from "./chunk-JKVUFP3G.js";
|
|
47
48
|
import {
|
|
48
|
-
ScreencastCapturer
|
|
49
|
-
|
|
49
|
+
ScreencastCapturer,
|
|
50
|
+
resolveScreenshotsDir
|
|
51
|
+
} from "./chunk-HBMEFSTB.js";
|
|
50
52
|
import "./chunk-KFQGP6VL.js";
|
|
51
53
|
|
|
52
54
|
// src/daemon/daemon-main.ts
|
|
53
55
|
import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, appendFileSync, unlinkSync } from "fs";
|
|
54
|
-
import { join as
|
|
56
|
+
import { join as join9 } from "path";
|
|
55
57
|
import { homedir as homedir7 } from "os";
|
|
56
58
|
import { startHttpServer } from "@dyyz1993/xcli-core";
|
|
57
59
|
|
|
58
60
|
// src/daemon/rpc-handlers.ts
|
|
59
|
-
import { readFileSync as
|
|
61
|
+
import { readFileSync as readFileSync7 } from "fs";
|
|
60
62
|
import {
|
|
61
63
|
createSessionMeta,
|
|
62
64
|
removeSession
|
|
@@ -64,8 +66,8 @@ import {
|
|
|
64
66
|
|
|
65
67
|
// src/executor.ts
|
|
66
68
|
import {
|
|
67
|
-
ok as
|
|
68
|
-
fail as
|
|
69
|
+
ok as ok28,
|
|
70
|
+
fail as fail13,
|
|
69
71
|
isCommandResult,
|
|
70
72
|
CompositeStorage as CompositeStorage2,
|
|
71
73
|
TipCollector as TipCollector2,
|
|
@@ -922,29 +924,441 @@ var mouseCommand = registerCommand({
|
|
|
922
924
|
}
|
|
923
925
|
});
|
|
924
926
|
|
|
925
|
-
// src/commands/
|
|
927
|
+
// src/commands/find-visual.ts
|
|
926
928
|
import { z as z7 } from "zod";
|
|
927
|
-
import { ok as ok7,
|
|
929
|
+
import { ok as ok7, fail as fail2 } from "@dyyz1993/xcli-core";
|
|
930
|
+
import { readFileSync, existsSync } from "fs";
|
|
931
|
+
import { homedir as homedir2 } from "os";
|
|
932
|
+
import { join as join2 } from "path";
|
|
933
|
+
function loadVLMCredentials() {
|
|
934
|
+
const candidates = [
|
|
935
|
+
process.env.XBROWSER_VLM_CONFIG,
|
|
936
|
+
join2(homedir2(), ".zcode", "v2", "config.json")
|
|
937
|
+
].filter(Boolean);
|
|
938
|
+
for (const file of candidates) {
|
|
939
|
+
try {
|
|
940
|
+
if (!existsSync(file)) continue;
|
|
941
|
+
const cfg = JSON.parse(readFileSync(file, "utf8"));
|
|
942
|
+
const provider = Object.values(cfg.provider ?? {})[0];
|
|
943
|
+
if (provider?.options?.apiKey && provider.options.baseURL) {
|
|
944
|
+
const models = Object.keys(provider.models ?? {});
|
|
945
|
+
const vl = models.find((m) => /vl|vision/i.test(m));
|
|
946
|
+
const flash = models.find((m) => /flash/i.test(m) && /5\.?3/i.test(m));
|
|
947
|
+
return {
|
|
948
|
+
apiKey: provider.options.apiKey,
|
|
949
|
+
baseURL: provider.options.baseURL.replace(/\/$/, ""),
|
|
950
|
+
model: process.env.XBROWSER_VLM_MODEL || vl || flash || models.find((m) => /5\.[23]/.test(m)) || models[0]
|
|
951
|
+
};
|
|
952
|
+
}
|
|
953
|
+
} catch {
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
return null;
|
|
957
|
+
}
|
|
958
|
+
async function locateByVLM(creds, imageBase64, target, feedback) {
|
|
959
|
+
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}"""
|
|
960
|
+
|
|
961
|
+
\u4E25\u683C\u53EA\u8F93\u51FA\u4E00\u4E2A JSON \u5BF9\u8C61\uFF08\u65E0 markdown\u3001\u65E0\u89E3\u91CA\uFF09\uFF1A
|
|
962
|
+
{"label":"<\u5143\u7D20\u540D>","box":{"x":<\u5DE6\u4E0Ax>,"y":<\u5DE6\u4E0Ay>,"width":<\u5BBD>,"height":<\u9AD8>},"confidence":"high|medium|low"}
|
|
963
|
+
\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
|
|
964
|
+
${feedback ? `\u6CE8\u610F\uFF1A${feedback}` : ""}`;
|
|
965
|
+
const resp = await fetch(creds.baseURL + "/v1/messages", {
|
|
966
|
+
method: "POST",
|
|
967
|
+
headers: { "content-type": "application/json", "x-api-key": creds.apiKey, "anthropic-version": "2023-06-01" },
|
|
968
|
+
body: JSON.stringify({
|
|
969
|
+
model: creds.model,
|
|
970
|
+
max_tokens: 2e3,
|
|
971
|
+
// thinking 模型的思考块也计入配额,300 会在 text 前截断
|
|
972
|
+
messages: [{
|
|
973
|
+
role: "user",
|
|
974
|
+
content: [
|
|
975
|
+
{ type: "image", source: { type: "base64", media_type: "image/png", data: imageBase64 } },
|
|
976
|
+
{ type: "text", text: prompt }
|
|
977
|
+
]
|
|
978
|
+
}]
|
|
979
|
+
}),
|
|
980
|
+
signal: AbortSignal.timeout(6e4)
|
|
981
|
+
});
|
|
982
|
+
if (!resp.ok) throw new Error(`VLM HTTP ${resp.status}`);
|
|
983
|
+
const data = await resp.json();
|
|
984
|
+
const text = (data.content ?? []).filter((c) => c.type === "text").map((c) => c.text ?? "").join("");
|
|
985
|
+
const m = text.match(/\{[\s\S]*\}/);
|
|
986
|
+
if (!m) {
|
|
987
|
+
console.error("[find-visual] VLM raw (no JSON):", text.substring(0, 200));
|
|
988
|
+
return null;
|
|
989
|
+
}
|
|
990
|
+
const hit = JSON.parse(m[0]);
|
|
991
|
+
if (!hit.label || !hit.box) return null;
|
|
992
|
+
return hit;
|
|
993
|
+
}
|
|
994
|
+
var findVisualCommand = registerCommand({
|
|
995
|
+
name: "find-visual",
|
|
996
|
+
description: "Visual element location: screenshot \u2192 VLM \u2192 page coordinates (optionally click)",
|
|
997
|
+
scope: "page",
|
|
998
|
+
parameters: z7.object({
|
|
999
|
+
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'),
|
|
1000
|
+
click: z7.boolean().optional().describe("\u627E\u5230\u540E\u7ACB\u5373\u70B9\u51FB\uFF08\u771F\u5B9E\u9F20\u6807\u4E8B\u4EF6\uFF09"),
|
|
1001
|
+
threshold: z7.coerce.number().optional().describe("\u7F6E\u4FE1\u5EA6\u9608\u503C\uFF0C\u4F4E\u4E8E\u5219\u62A5 not-found\uFF08\u9ED8\u8BA4 0\uFF09"),
|
|
1002
|
+
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")
|
|
1003
|
+
}),
|
|
1004
|
+
result: z7.object({
|
|
1005
|
+
found: z7.boolean(),
|
|
1006
|
+
element: z7.string(),
|
|
1007
|
+
box: z7.unknown().optional(),
|
|
1008
|
+
center: z7.unknown().optional(),
|
|
1009
|
+
confidence: z7.string().optional(),
|
|
1010
|
+
model: z7.string().optional(),
|
|
1011
|
+
clicked: z7.boolean().optional(),
|
|
1012
|
+
screenshot: z7.string().optional()
|
|
1013
|
+
}),
|
|
1014
|
+
handler: async (p, ctx) => {
|
|
1015
|
+
const page = ctx.page;
|
|
1016
|
+
const creds = loadVLMCredentials();
|
|
1017
|
+
if (!creds) {
|
|
1018
|
+
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", [
|
|
1019
|
+
"\u4E5F\u53EF\u7528 XBROWSER_VLM_MODEL \u6307\u5B9A\u6A21\u578B\uFF08\u9ED8\u8BA4\u81EA\u52A8\u9009 VL/\u65D7\u8230\u6A21\u578B\uFF09"
|
|
1020
|
+
]);
|
|
1021
|
+
}
|
|
1022
|
+
const buffer = await page.screenshot({ type: "png" });
|
|
1023
|
+
const imageBase64 = buffer.toString("base64");
|
|
1024
|
+
const imgW = buffer.readUInt32BE(16);
|
|
1025
|
+
const imgH = buffer.readUInt32BE(20);
|
|
1026
|
+
let hit = null;
|
|
1027
|
+
let feedback = "";
|
|
1028
|
+
try {
|
|
1029
|
+
for (let attempt = 0; attempt < 3 && !hit; attempt++) {
|
|
1030
|
+
const candidate = await locateByVLM(creds, imageBase64, p.element, feedback);
|
|
1031
|
+
if (!candidate) break;
|
|
1032
|
+
const b = candidate.box;
|
|
1033
|
+
const inBounds = b.x >= 0 && b.y >= 0 && b.x + b.width <= imgW + 2 && b.y + b.height <= imgH + 2;
|
|
1034
|
+
const sane = b.width > 2 && b.height > 2;
|
|
1035
|
+
if (inBounds && sane) {
|
|
1036
|
+
hit = candidate;
|
|
1037
|
+
break;
|
|
1038
|
+
}
|
|
1039
|
+
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`;
|
|
1040
|
+
}
|
|
1041
|
+
} catch (e) {
|
|
1042
|
+
return fail2(`VLM \u8C03\u7528\u5931\u8D25: ${e instanceof Error ? e.message : String(e)}`);
|
|
1043
|
+
}
|
|
1044
|
+
if (!hit) {
|
|
1045
|
+
return ok7({ found: false, element: p.element, model: creds.model }, [
|
|
1046
|
+
`\u622A\u56FE\u4E2D\u672A\u627E\u5230: ${p.element}`
|
|
1047
|
+
]);
|
|
1048
|
+
}
|
|
1049
|
+
if (p.zoom !== false && hit) {
|
|
1050
|
+
try {
|
|
1051
|
+
const pad = Math.max(20, Math.round(Math.max(hit.box.width, hit.box.height) * 0.5));
|
|
1052
|
+
const clip = {
|
|
1053
|
+
x: Math.max(0, hit.box.x - pad),
|
|
1054
|
+
y: Math.max(0, hit.box.y - pad),
|
|
1055
|
+
width: Math.min(imgW - Math.max(0, hit.box.x - pad), hit.box.width + pad * 2),
|
|
1056
|
+
height: Math.min(imgH - Math.max(0, hit.box.y - pad), hit.box.height + pad * 2)
|
|
1057
|
+
};
|
|
1058
|
+
if (clip.width > 8 && clip.height > 8) {
|
|
1059
|
+
const zoomBuf = await page.screenshot({ type: "png", clip });
|
|
1060
|
+
const zoomB64 = zoomBuf.toString("base64");
|
|
1061
|
+
const zoomW = zoomBuf.readUInt32BE(16);
|
|
1062
|
+
const zoomH = zoomBuf.readUInt32BE(20);
|
|
1063
|
+
const refined = await locateByVLM(creds, zoomB64, p.element);
|
|
1064
|
+
if (refined) {
|
|
1065
|
+
const rb = refined.box;
|
|
1066
|
+
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;
|
|
1067
|
+
if (inZoom) {
|
|
1068
|
+
hit = {
|
|
1069
|
+
label: refined.label || hit.label,
|
|
1070
|
+
box: {
|
|
1071
|
+
x: clip.x + rb.x,
|
|
1072
|
+
y: clip.y + rb.y,
|
|
1073
|
+
width: rb.width,
|
|
1074
|
+
height: rb.height
|
|
1075
|
+
},
|
|
1076
|
+
confidence: refined.confidence === "low" ? hit.confidence : refined.confidence
|
|
1077
|
+
};
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
} catch {
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
if (p.threshold !== void 0) {
|
|
1085
|
+
const rank = { high: 3, medium: 2, low: 1 };
|
|
1086
|
+
const thresholdName = p.threshold >= 3 ? "high" : p.threshold >= 2 ? "medium" : "low";
|
|
1087
|
+
if ((rank[hit.confidence ?? "low"] ?? 1) < (rank[thresholdName] ?? 1)) {
|
|
1088
|
+
return ok7({ found: false, element: p.element, confidence: hit.confidence, box: hit.box, model: creds.model }, [
|
|
1089
|
+
`\u7F6E\u4FE1\u5EA6 ${hit.confidence} \u4F4E\u4E8E\u9608\u503C ${p.threshold}`
|
|
1090
|
+
]);
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
const viewport = await page.evaluate("JSON.stringify({w: window.innerWidth, h: window.innerHeight})").then((v) => JSON.parse(String(v))).catch(() => ({ w: 1280, h: 800 }));
|
|
1094
|
+
const pngW = buffer.readUInt32BE(16);
|
|
1095
|
+
const pngH = buffer.readUInt32BE(20);
|
|
1096
|
+
const scaleX = viewport.w / pngW;
|
|
1097
|
+
const scaleY = viewport.h / pngH;
|
|
1098
|
+
const center = {
|
|
1099
|
+
x: Math.round((hit.box.x + hit.box.width / 2) * scaleX),
|
|
1100
|
+
y: Math.round((hit.box.y + hit.box.height / 2) * scaleY)
|
|
1101
|
+
};
|
|
1102
|
+
let clicked = false;
|
|
1103
|
+
if (p.click) {
|
|
1104
|
+
await page.mouse.click(center.x, center.y, { stealth: true });
|
|
1105
|
+
clicked = true;
|
|
1106
|
+
}
|
|
1107
|
+
return ok7({
|
|
1108
|
+
found: true,
|
|
1109
|
+
element: p.element,
|
|
1110
|
+
box: hit.box,
|
|
1111
|
+
center,
|
|
1112
|
+
confidence: hit.confidence,
|
|
1113
|
+
model: creds.model,
|
|
1114
|
+
clicked,
|
|
1115
|
+
screenshot: `data:image/png;base64,${imageBase64.substring(0, 64)}... (${Math.round(imageBase64.length / 1024)}KB)`
|
|
1116
|
+
}, [
|
|
1117
|
+
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`
|
|
1118
|
+
]);
|
|
1119
|
+
}
|
|
1120
|
+
});
|
|
1121
|
+
|
|
1122
|
+
// src/commands/vision-task.ts
|
|
1123
|
+
import { z as z8 } from "zod";
|
|
1124
|
+
import { ok as ok8, fail as fail3 } from "@dyyz1993/xcli-core";
|
|
1125
|
+
import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
|
|
1126
|
+
import { homedir as homedir3 } from "os";
|
|
1127
|
+
import { join as join3 } from "path";
|
|
1128
|
+
function loadVLMCredentials2() {
|
|
1129
|
+
const candidates = [
|
|
1130
|
+
process.env.XBROWSER_VLM_CONFIG,
|
|
1131
|
+
join3(homedir3(), ".zcode", "v2", "config.json")
|
|
1132
|
+
].filter(Boolean);
|
|
1133
|
+
for (const file of candidates) {
|
|
1134
|
+
try {
|
|
1135
|
+
if (!existsSync2(file)) continue;
|
|
1136
|
+
const cfg = JSON.parse(readFileSync2(file, "utf8"));
|
|
1137
|
+
const provider = Object.values(cfg.provider ?? {})[0];
|
|
1138
|
+
if (provider?.options?.apiKey && provider.options.baseURL) {
|
|
1139
|
+
const models = Object.keys(provider.models ?? {});
|
|
1140
|
+
const vl = models.find((m) => /vl|vision/i.test(m));
|
|
1141
|
+
const flash = models.find((m) => /flash/i.test(m) && /5\.?3/i.test(m));
|
|
1142
|
+
return {
|
|
1143
|
+
apiKey: provider.options.apiKey,
|
|
1144
|
+
baseURL: provider.options.baseURL.replace(/\/$/, ""),
|
|
1145
|
+
model: process.env.XBROWSER_VLM_MODEL || vl || flash || models.find((m) => /5\.[23]/.test(m)) || models[0]
|
|
1146
|
+
};
|
|
1147
|
+
}
|
|
1148
|
+
} catch {
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
return null;
|
|
1152
|
+
}
|
|
1153
|
+
async function vlmAsk(creds, content, maxTokens = 2e3) {
|
|
1154
|
+
const resp = await fetch(creds.baseURL + "/v1/messages", {
|
|
1155
|
+
method: "POST",
|
|
1156
|
+
headers: { "content-type": "application/json", "x-api-key": creds.apiKey, "anthropic-version": "2023-06-01" },
|
|
1157
|
+
body: JSON.stringify({ model: creds.model, max_tokens: maxTokens, messages: [{ role: "user", content }] }),
|
|
1158
|
+
signal: AbortSignal.timeout(9e4)
|
|
1159
|
+
});
|
|
1160
|
+
if (!resp.ok) throw new Error(`VLM HTTP ${resp.status}`);
|
|
1161
|
+
const data = await resp.json();
|
|
1162
|
+
return (data.content ?? []).filter((c) => c.type === "text").map((c) => c.text ?? "").join("");
|
|
1163
|
+
}
|
|
1164
|
+
async function vlmAskRetry(creds, content, maxTokens = 2e3) {
|
|
1165
|
+
let last = "";
|
|
1166
|
+
for (let i = 0; i < 2; i++) {
|
|
1167
|
+
last = await vlmAsk(creds, content, maxTokens);
|
|
1168
|
+
if (last.trim()) return last;
|
|
1169
|
+
await new Promise((r) => setTimeout(r, 1200));
|
|
1170
|
+
}
|
|
1171
|
+
return last;
|
|
1172
|
+
}
|
|
1173
|
+
function pngDims(b64) {
|
|
1174
|
+
const buf = Buffer.from(b64, "base64");
|
|
1175
|
+
return { w: buf.readUInt32BE(16), h: buf.readUInt32BE(20) };
|
|
1176
|
+
}
|
|
1177
|
+
function parseDecision(text) {
|
|
1178
|
+
const m = text.match(/\{[\s\S]*\}/);
|
|
1179
|
+
if (!m) return null;
|
|
1180
|
+
try {
|
|
1181
|
+
const j = JSON.parse(m[0]);
|
|
1182
|
+
if (!j.action || !j.action.type) return null;
|
|
1183
|
+
return j;
|
|
1184
|
+
} catch {
|
|
1185
|
+
return null;
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
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
|
|
1189
|
+
|
|
1190
|
+
\u4E25\u683C\u53EA\u8F93\u51FA\u4E00\u4E2A JSON\uFF08\u65E0 markdown\uFF09\uFF1A
|
|
1191
|
+
{"thought":"<\u7B80\u77ED\u63A8\u7406>","action":{"type":"click","x":<int>,"y":<int>}
|
|
1192
|
+
\u6216 {"type":"type","text":"<\u8981\u8F93\u5165\u7684\u6587\u5B57>"}\uFF08\u8F93\u5165\u524D\u987B\u5DF2\u70B9\u51FB\u805A\u7126\u8F93\u5165\u6846\uFF09
|
|
1193
|
+
\u6216 {"type":"key","key":"Enter"}\uFF08key \u540D\uFF1AEnter/Tab/Escape/ArrowDown\u2026\uFF09
|
|
1194
|
+
\u6216 {"type":"scroll","direction":"down","amount":<px>}
|
|
1195
|
+
\u6216 {"type":"done","summary":"<\u4EFB\u52A1\u5B8C\u6210\u6458\u8981>"}
|
|
1196
|
+
\u6216 {"type":"stuck","reason":"<\u65E0\u6CD5\u7EE7\u7EED\u7684\u539F\u56E0>"}}
|
|
1197
|
+
|
|
1198
|
+
\u5750\u6807\u4EE5\u622A\u56FE\u5DE6\u4E0A\u89D2\u4E3A\u539F\u70B9\u3001\u50CF\u7D20\u4E3A\u5355\u4F4D\u3002\u89C4\u5219\uFF1A
|
|
1199
|
+
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
|
|
1200
|
+
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
|
|
1201
|
+
3. \u4EFB\u52A1\u76EE\u6807\u8FBE\u6210\u7ACB\u5373\u8F93\u51FA done\uFF1B5 \u6B65\u65E0\u8FDB\u5C55\u8F93\u51FA stuck`;
|
|
1202
|
+
var visionTaskCommand = registerCommand({
|
|
1203
|
+
name: "vision-task",
|
|
1204
|
+
description: "Vision-loop agent: screenshot \u2192 VLM decide \u2192 CDP act \u2192 verify, until done",
|
|
1205
|
+
scope: "page",
|
|
1206
|
+
parameters: z8.object({
|
|
1207
|
+
task: z8.string().describe('\u81EA\u7136\u8BED\u8A00\u4EFB\u52A1\uFF0C\u5982 "\u641C\u7D22 xbrowser \u5E76\u6253\u5F00\u7B2C\u4E00\u7BC7\u6587\u7AE0"'),
|
|
1208
|
+
maxSteps: z8.coerce.number().optional().describe("\u6700\u5927\u6B65\u6570\uFF08\u9ED8\u8BA4 12\uFF09"),
|
|
1209
|
+
settleMs: z8.coerce.number().optional().describe("\u6BCF\u6B65\u540E\u7B49\u5F85\u9875\u9762\u7A33\u5B9A\u7684\u6BEB\u79D2\u6570\uFF08\u9ED8\u8BA4 1500\uFF09")
|
|
1210
|
+
}),
|
|
1211
|
+
result: z8.object({
|
|
1212
|
+
done: z8.boolean(),
|
|
1213
|
+
steps: z8.unknown().optional(),
|
|
1214
|
+
summary: z8.string().optional(),
|
|
1215
|
+
model: z8.string().optional()
|
|
1216
|
+
}),
|
|
1217
|
+
handler: async (p, ctx) => {
|
|
1218
|
+
let page = ctx.page;
|
|
1219
|
+
const creds = loadVLMCredentials2();
|
|
1220
|
+
if (!creds) {
|
|
1221
|
+
return fail3("\u672A\u627E\u5230 VLM \u51ED\u636E\uFF1A\u9700\u8981 ~/.zcode/v2/config.json \u6216 XBROWSER_VLM_CONFIG");
|
|
1222
|
+
}
|
|
1223
|
+
const maxSteps = p.maxSteps ?? 12;
|
|
1224
|
+
const settleMs = p.settleMs ?? 1500;
|
|
1225
|
+
const steps = [];
|
|
1226
|
+
const history = [];
|
|
1227
|
+
let lastUrl = page.url();
|
|
1228
|
+
for (let step = 1; step <= maxSteps; step++) {
|
|
1229
|
+
if (page.url() === "about:blank") {
|
|
1230
|
+
try {
|
|
1231
|
+
const ctxObj = page.context?.();
|
|
1232
|
+
const pages = ctxObj?.pages?.() || [];
|
|
1233
|
+
let real;
|
|
1234
|
+
for (let i = pages.length - 1; i >= 0; i--) {
|
|
1235
|
+
const u = pages[i]?.url?.() ?? "";
|
|
1236
|
+
if (u && u !== "about:blank") {
|
|
1237
|
+
real = pages[i];
|
|
1238
|
+
break;
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
if (real) {
|
|
1242
|
+
await real.bringToFront().catch(() => {
|
|
1243
|
+
});
|
|
1244
|
+
page = real;
|
|
1245
|
+
}
|
|
1246
|
+
} catch {
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
const buffer = await page.screenshot({ type: "png" });
|
|
1250
|
+
const b64 = buffer.toString("base64");
|
|
1251
|
+
const { w, h } = pngDims(b64);
|
|
1252
|
+
const obsText = [
|
|
1253
|
+
`\u4EFB\u52A1\uFF1A${p.task}`,
|
|
1254
|
+
`\u622A\u56FE\u5C3A\u5BF8\uFF1A${w}x${h}\uFF08\u5750\u6807\u539F\u70B9\uFF1A\u5DE6\u4E0A\u89D2\uFF09`,
|
|
1255
|
+
`\u5F53\u524D URL\uFF1A${page.url()}`,
|
|
1256
|
+
history.length ? `\u5DF2\u6267\u884C\u52A8\u4F5C\uFF1A
|
|
1257
|
+
${history.slice(-6).join("\n")}` : "\u5C1A\u65E0\u52A8\u4F5C\u3002"
|
|
1258
|
+
].join("\n");
|
|
1259
|
+
const reply = await vlmAskRetry(creds, [
|
|
1260
|
+
{ type: "image", source: { type: "base64", media_type: "image/png", data: b64 } },
|
|
1261
|
+
{ type: "text", text: `${SYSTEM_PROMPT}
|
|
1262
|
+
|
|
1263
|
+
${obsText}` }
|
|
1264
|
+
]);
|
|
1265
|
+
const decision = parseDecision(reply);
|
|
1266
|
+
if (!decision) {
|
|
1267
|
+
steps.push({ n: step, thought: "(unparseable)", action: "stop", ts: Date.now() });
|
|
1268
|
+
return ok8({ done: false, steps, summary: `\u7B2C ${step} \u6B65 VLM \u8F93\u51FA\u65E0\u6CD5\u89E3\u6790: ${reply.substring(0, 120)}`, model: creds.model });
|
|
1269
|
+
}
|
|
1270
|
+
const a = decision.action;
|
|
1271
|
+
let acted = "";
|
|
1272
|
+
try {
|
|
1273
|
+
if (a.type === "done") {
|
|
1274
|
+
steps.push({ n: step, thought: decision.thought, action: "done", ts: Date.now() });
|
|
1275
|
+
return ok8({ done: true, steps, summary: a.summary, model: creds.model });
|
|
1276
|
+
}
|
|
1277
|
+
if (a.type === "stuck") {
|
|
1278
|
+
steps.push({ n: step, thought: decision.thought, action: "stuck", ts: Date.now() });
|
|
1279
|
+
return ok8({ done: false, steps, summary: `stuck: ${a.reason}`, model: creds.model });
|
|
1280
|
+
}
|
|
1281
|
+
if (a.type === "click") {
|
|
1282
|
+
const cx = Math.max(2, Math.min(w - 2, Math.round(a.x)));
|
|
1283
|
+
const cy = Math.max(2, Math.min(h - 2, Math.round(a.y)));
|
|
1284
|
+
await page.mouse.click(cx, cy, { stealth: true });
|
|
1285
|
+
acted = `click(${cx},${cy})`;
|
|
1286
|
+
} else if (a.type === "type") {
|
|
1287
|
+
await page.keyboard.type(a.text, { stealth: true });
|
|
1288
|
+
acted = `type("${a.text.substring(0, 30)}")`;
|
|
1289
|
+
} else if (a.type === "key") {
|
|
1290
|
+
await page.keyboard.press(a.key);
|
|
1291
|
+
acted = `key(${a.key})`;
|
|
1292
|
+
} else if (a.type === "scroll") {
|
|
1293
|
+
await page.mouse.wheel(0, a.direction === "down" ? a.amount : -a.amount);
|
|
1294
|
+
acted = `scroll(${a.direction},${a.amount})`;
|
|
1295
|
+
}
|
|
1296
|
+
} catch (e) {
|
|
1297
|
+
acted = `${a.type} FAILED: ${e instanceof Error ? e.message : String(e).substring(0, 80)}`;
|
|
1298
|
+
}
|
|
1299
|
+
steps.push({ n: step, thought: decision.thought, action: acted, ts: Date.now() });
|
|
1300
|
+
history.push(`${step}. ${acted} \u2014 ${decision.thought.substring(0, 60)}`);
|
|
1301
|
+
await new Promise((r) => setTimeout(r, settleMs));
|
|
1302
|
+
if (a.type === "click") {
|
|
1303
|
+
try {
|
|
1304
|
+
const ctxObj = page.context?.();
|
|
1305
|
+
const pages = ctxObj?.pages?.() || [];
|
|
1306
|
+
for (let i = pages.length - 1; i >= 0; i--) {
|
|
1307
|
+
const cand = pages[i];
|
|
1308
|
+
if (!cand || cand === page) continue;
|
|
1309
|
+
const u = cand.url?.() ?? "";
|
|
1310
|
+
if (!u || u === "about:blank") continue;
|
|
1311
|
+
if (u === page.url()) break;
|
|
1312
|
+
await cand.bringToFront().catch(() => {
|
|
1313
|
+
});
|
|
1314
|
+
page = cand;
|
|
1315
|
+
break;
|
|
1316
|
+
}
|
|
1317
|
+
} catch {
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
const urlNow = page.url();
|
|
1321
|
+
if (urlNow !== lastUrl) {
|
|
1322
|
+
history.push(` [\u9875\u9762\u8DF3\u8F6C \u2192 ${urlNow.substring(0, 80)}]`);
|
|
1323
|
+
lastUrl = urlNow;
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
return ok8({ done: false, steps, summary: `\u5DF2\u8FBE\u6700\u5927\u6B65\u6570 ${maxSteps}`, model: creds.model });
|
|
1327
|
+
}
|
|
1328
|
+
});
|
|
1329
|
+
|
|
1330
|
+
// src/commands/evaluate.ts
|
|
1331
|
+
import { z as z9 } from "zod";
|
|
1332
|
+
import { ok as ok9, normalizeTips as normalizeTips2 } from "@dyyz1993/xcli-core";
|
|
928
1333
|
var evaluateCommand = registerCommand({
|
|
929
1334
|
name: "eval",
|
|
930
1335
|
description: "Evaluate JavaScript expression in the browser (--frame <url-substring> \u53EF\u5B9A\u5411\u5230 iframe \u4E0A\u4E0B\u6587\uFF0C\u8DE8\u57DF iframe \u4EA6\u53EF)",
|
|
931
1336
|
scope: "page",
|
|
932
|
-
parameters:
|
|
933
|
-
expression:
|
|
934
|
-
frame:
|
|
1337
|
+
parameters: z9.object({
|
|
1338
|
+
expression: z9.string().optional(),
|
|
1339
|
+
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"),
|
|
1340
|
+
// Internal: base64url-encoded script set by the router for `eval <js...>`,
|
|
1341
|
+
// so multiline/semicolon JS survives chain parsing (which splits on ';'
|
|
1342
|
+
// and whitespace). Not intended for direct CLI use.
|
|
1343
|
+
"script-b64": z9.string().optional().describe("Internal: base64url script set by the CLI router; do not pass directly")
|
|
935
1344
|
}),
|
|
936
1345
|
handler: async (p, ctx) => {
|
|
1346
|
+
const rawScript = p["script-b64"];
|
|
1347
|
+
const expression = rawScript ? Buffer.from(rawScript, "base64url").toString("utf8") : p.expression;
|
|
1348
|
+
if (!expression || !expression.trim()) {
|
|
1349
|
+
return { ok: false, error: "eval: expression is required" };
|
|
1350
|
+
}
|
|
937
1351
|
const engine = createRuleEngine();
|
|
938
1352
|
engine.start();
|
|
939
1353
|
const decision = engine.evaluate({
|
|
940
1354
|
method: "Runtime.evaluate",
|
|
941
|
-
params: { expression
|
|
1355
|
+
params: { expression },
|
|
942
1356
|
sessionId: "eval-check",
|
|
943
1357
|
direction: "client\u2192browser"
|
|
944
1358
|
});
|
|
945
1359
|
engine.stop();
|
|
946
|
-
const result = p.frame ? await ctx.page.evaluateInFrame(p.frame,
|
|
947
|
-
const response =
|
|
1360
|
+
const result = p.frame ? await ctx.page.evaluateInFrame(p.frame, expression) : await ctx.page.evaluate(expression);
|
|
1361
|
+
const response = ok9({ result });
|
|
948
1362
|
if (decision && decision.severity === "danger") {
|
|
949
1363
|
response.tips = normalizeTips2([`${decision.suggestion}`]);
|
|
950
1364
|
}
|
|
@@ -952,37 +1366,65 @@ var evaluateCommand = registerCommand({
|
|
|
952
1366
|
}
|
|
953
1367
|
});
|
|
954
1368
|
|
|
1369
|
+
// src/commands/ua.ts
|
|
1370
|
+
import { z as z10 } from "zod";
|
|
1371
|
+
import { ok as ok10 } from "@dyyz1993/xcli-core";
|
|
1372
|
+
function uaHookScript(ua) {
|
|
1373
|
+
const uaJson = JSON.stringify(ua);
|
|
1374
|
+
const appVersion = JSON.stringify(ua.replace(/^Mozilla\//, ""));
|
|
1375
|
+
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){}`;
|
|
1376
|
+
}
|
|
1377
|
+
var uaCommand = registerCommand({
|
|
1378
|
+
name: "ua",
|
|
1379
|
+
description: "Get or override the session User-Agent (anti-bot UA detection countermeasure)",
|
|
1380
|
+
scope: "page",
|
|
1381
|
+
parameters: z10.object({
|
|
1382
|
+
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')
|
|
1383
|
+
}),
|
|
1384
|
+
handler: async (p, ctx) => {
|
|
1385
|
+
if (!p.value) {
|
|
1386
|
+
const current = await ctx.page.evaluate("navigator.userAgent");
|
|
1387
|
+
return ok10({ userAgent: current });
|
|
1388
|
+
}
|
|
1389
|
+
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;
|
|
1390
|
+
const script = uaHookScript(ua);
|
|
1391
|
+
await ctx.page.addInitScript(script);
|
|
1392
|
+
await ctx.page.evaluate(`(function(){${script}})()`);
|
|
1393
|
+
return ok10({ userAgent: ua, mode: "init-script" });
|
|
1394
|
+
}
|
|
1395
|
+
});
|
|
1396
|
+
|
|
955
1397
|
// src/commands/storage.ts
|
|
956
|
-
import { z as
|
|
957
|
-
import { ok as
|
|
1398
|
+
import { z as z11 } from "zod";
|
|
1399
|
+
import { ok as ok11, fail as fail4 } from "@dyyz1993/xcli-core";
|
|
958
1400
|
var getCookiesCommand = registerCommand({
|
|
959
1401
|
name: "get-cookies",
|
|
960
1402
|
description: "Get all cookies for the current page",
|
|
961
1403
|
scope: "page",
|
|
962
|
-
result:
|
|
963
|
-
cookies:
|
|
1404
|
+
result: z11.object({
|
|
1405
|
+
cookies: z11.array(z11.record(z11.unknown()))
|
|
964
1406
|
}),
|
|
965
1407
|
handler: async (_p, ctx) => {
|
|
966
1408
|
const cookies = await ctx.browserContext.cookies();
|
|
967
|
-
return
|
|
1409
|
+
return ok11({ cookies });
|
|
968
1410
|
}
|
|
969
1411
|
});
|
|
970
1412
|
var setCookieCommand = registerCommand({
|
|
971
1413
|
name: "set-cookie",
|
|
972
1414
|
description: "Set a cookie",
|
|
973
1415
|
scope: "page",
|
|
974
|
-
parameters:
|
|
975
|
-
name:
|
|
976
|
-
value:
|
|
977
|
-
domain:
|
|
978
|
-
path:
|
|
979
|
-
url:
|
|
980
|
-
expires:
|
|
981
|
-
httpOnly:
|
|
982
|
-
secure:
|
|
983
|
-
sameSite:
|
|
1416
|
+
parameters: z11.object({
|
|
1417
|
+
name: z11.coerce.string(),
|
|
1418
|
+
value: z11.coerce.string(),
|
|
1419
|
+
domain: z11.coerce.string().optional(),
|
|
1420
|
+
path: z11.coerce.string().optional(),
|
|
1421
|
+
url: z11.string().optional().describe("Cookie URL (alternative to domain)"),
|
|
1422
|
+
expires: z11.number().optional(),
|
|
1423
|
+
httpOnly: z11.boolean().optional(),
|
|
1424
|
+
secure: z11.boolean().optional(),
|
|
1425
|
+
sameSite: z11.enum(["Strict", "Lax", "None"]).optional()
|
|
984
1426
|
}),
|
|
985
|
-
result:
|
|
1427
|
+
result: z11.object({ name: z11.string() }),
|
|
986
1428
|
handler: async (p, ctx) => {
|
|
987
1429
|
const cookie = { ...p };
|
|
988
1430
|
if (!cookie.domain && !cookie.url) {
|
|
@@ -997,38 +1439,38 @@ var setCookieCommand = registerCommand({
|
|
|
997
1439
|
}
|
|
998
1440
|
}
|
|
999
1441
|
if (!cookie.domain && !cookie.url) {
|
|
1000
|
-
return
|
|
1442
|
+
return fail4("set-cookie requires --domain or --url, or a non-blank page URL to infer from");
|
|
1001
1443
|
}
|
|
1002
1444
|
await ctx.browserContext.addCookies([cookie]);
|
|
1003
|
-
return
|
|
1445
|
+
return ok11({ name: p.name });
|
|
1004
1446
|
}
|
|
1005
1447
|
});
|
|
1006
1448
|
var clearCookiesCommand = registerCommand({
|
|
1007
1449
|
name: "clear-cookies",
|
|
1008
1450
|
description: "Clear all cookies",
|
|
1009
1451
|
scope: "page",
|
|
1010
|
-
result:
|
|
1452
|
+
result: z11.object({ cleared: z11.boolean() }),
|
|
1011
1453
|
handler: async (_p, ctx) => {
|
|
1012
1454
|
await ctx.browserContext.clearCookies();
|
|
1013
|
-
return
|
|
1455
|
+
return ok11({ cleared: true });
|
|
1014
1456
|
}
|
|
1015
1457
|
});
|
|
1016
1458
|
var getLocalStorageCommand = registerCommand({
|
|
1017
1459
|
name: "get-local-storage",
|
|
1018
1460
|
description: "Get localStorage entries",
|
|
1019
1461
|
scope: "page",
|
|
1020
|
-
parameters:
|
|
1021
|
-
key:
|
|
1462
|
+
parameters: z11.object({
|
|
1463
|
+
key: z11.string().optional()
|
|
1022
1464
|
}),
|
|
1023
|
-
result:
|
|
1024
|
-
|
|
1025
|
-
|
|
1465
|
+
result: z11.union([
|
|
1466
|
+
z11.object({ key: z11.string(), value: z11.string().nullable() }),
|
|
1467
|
+
z11.object({ data: z11.record(z11.string()) })
|
|
1026
1468
|
]),
|
|
1027
1469
|
handler: async (p, ctx) => {
|
|
1028
1470
|
try {
|
|
1029
1471
|
if (p.key) {
|
|
1030
1472
|
const value = await ctx.page.evaluate((k) => localStorage.getItem(k), p.key);
|
|
1031
|
-
return
|
|
1473
|
+
return ok11({ key: p.key, value });
|
|
1032
1474
|
}
|
|
1033
1475
|
const data = await ctx.page.evaluate(() => {
|
|
1034
1476
|
const entries = {};
|
|
@@ -1038,9 +1480,9 @@ var getLocalStorageCommand = registerCommand({
|
|
|
1038
1480
|
}
|
|
1039
1481
|
return entries;
|
|
1040
1482
|
});
|
|
1041
|
-
return
|
|
1483
|
+
return ok11({ data });
|
|
1042
1484
|
} catch (e) {
|
|
1043
|
-
return
|
|
1485
|
+
return fail4(`localStorage not accessible on this page: ${e instanceof Error ? e.message : String(e)}`);
|
|
1044
1486
|
}
|
|
1045
1487
|
}
|
|
1046
1488
|
});
|
|
@@ -1048,11 +1490,11 @@ var setLocalStorageCommand = registerCommand({
|
|
|
1048
1490
|
name: "set-local-storage",
|
|
1049
1491
|
description: "Set a localStorage entry",
|
|
1050
1492
|
scope: "page",
|
|
1051
|
-
parameters:
|
|
1052
|
-
key:
|
|
1053
|
-
value:
|
|
1493
|
+
parameters: z11.object({
|
|
1494
|
+
key: z11.string(),
|
|
1495
|
+
value: z11.string()
|
|
1054
1496
|
}),
|
|
1055
|
-
result:
|
|
1497
|
+
result: z11.object({ key: z11.string() }),
|
|
1056
1498
|
handler: async (p, ctx) => {
|
|
1057
1499
|
try {
|
|
1058
1500
|
await ctx.page.evaluate(
|
|
@@ -1061,9 +1503,9 @@ var setLocalStorageCommand = registerCommand({
|
|
|
1061
1503
|
},
|
|
1062
1504
|
{ key: p.key, value: p.value }
|
|
1063
1505
|
);
|
|
1064
|
-
return
|
|
1506
|
+
return ok11({ key: p.key });
|
|
1065
1507
|
} catch (e) {
|
|
1066
|
-
return
|
|
1508
|
+
return fail4(`localStorage not accessible on this page: ${e instanceof Error ? e.message : String(e)}`);
|
|
1067
1509
|
}
|
|
1068
1510
|
}
|
|
1069
1511
|
});
|
|
@@ -1071,26 +1513,24 @@ var clearLocalStorageCommand = registerCommand({
|
|
|
1071
1513
|
name: "clear-local-storage",
|
|
1072
1514
|
description: "Clear all localStorage entries",
|
|
1073
1515
|
scope: "page",
|
|
1074
|
-
result:
|
|
1516
|
+
result: z11.object({ cleared: z11.boolean() }),
|
|
1075
1517
|
handler: async (_p, ctx) => {
|
|
1076
1518
|
try {
|
|
1077
1519
|
await ctx.page.evaluate(() => localStorage.clear());
|
|
1078
|
-
return
|
|
1520
|
+
return ok11({ cleared: true });
|
|
1079
1521
|
} catch (e) {
|
|
1080
|
-
return
|
|
1522
|
+
return fail4(`localStorage not accessible on this page: ${e instanceof Error ? e.message : String(e)}`);
|
|
1081
1523
|
}
|
|
1082
1524
|
}
|
|
1083
1525
|
});
|
|
1084
1526
|
|
|
1085
1527
|
// src/commands/screenshot.ts
|
|
1086
|
-
import { z as
|
|
1087
|
-
import { ok as
|
|
1528
|
+
import { z as z12 } from "zod";
|
|
1529
|
+
import { ok as ok12, fail as fail5 } from "@dyyz1993/xcli-core";
|
|
1088
1530
|
import { writeFileSync, mkdirSync } from "fs";
|
|
1089
|
-
import { dirname, join as
|
|
1090
|
-
import { homedir as homedir2 } from "os";
|
|
1091
|
-
var SCREENSHOTS_DIR = join2(homedir2(), ".xbrowser", "screenshots");
|
|
1531
|
+
import { dirname, join as join4 } from "path";
|
|
1092
1532
|
function ensureScreenshotsDir() {
|
|
1093
|
-
mkdirSync(
|
|
1533
|
+
mkdirSync(resolveScreenshotsDir(), { recursive: true });
|
|
1094
1534
|
}
|
|
1095
1535
|
function ensureParentDir(filePath) {
|
|
1096
1536
|
const dir = dirname(filePath);
|
|
@@ -1106,30 +1546,30 @@ function generateScreenshotPath(format) {
|
|
|
1106
1546
|
const timestamp = Date.now();
|
|
1107
1547
|
const random = Math.random().toString(36).slice(2, 8);
|
|
1108
1548
|
const ext = format === "jpeg" ? "jpg" : "png";
|
|
1109
|
-
return
|
|
1549
|
+
return join4(resolveScreenshotsDir(), `screenshot-${timestamp}-${random}.${ext}`);
|
|
1110
1550
|
}
|
|
1111
1551
|
var screenshotCommand = registerCommand({
|
|
1112
1552
|
name: "screenshot",
|
|
1113
|
-
description: "Take a screenshot of the page or element",
|
|
1553
|
+
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>)",
|
|
1114
1554
|
scope: "page",
|
|
1115
1555
|
selectorParams: ["selector"],
|
|
1116
|
-
parameters:
|
|
1117
|
-
selector:
|
|
1118
|
-
type:
|
|
1119
|
-
fullPage:
|
|
1120
|
-
output:
|
|
1121
|
-
base64:
|
|
1556
|
+
parameters: z12.object({
|
|
1557
|
+
selector: z12.string().optional(),
|
|
1558
|
+
type: z12.enum(["png", "jpeg"]).optional(),
|
|
1559
|
+
fullPage: z12.boolean().optional(),
|
|
1560
|
+
output: z12.string().optional(),
|
|
1561
|
+
base64: z12.boolean().optional().describe("Return base64 data instead of file path")
|
|
1122
1562
|
}),
|
|
1123
|
-
result:
|
|
1124
|
-
|
|
1125
|
-
data:
|
|
1126
|
-
format:
|
|
1127
|
-
size:
|
|
1563
|
+
result: z12.union([
|
|
1564
|
+
z12.object({
|
|
1565
|
+
data: z12.string(),
|
|
1566
|
+
format: z12.string(),
|
|
1567
|
+
size: z12.number()
|
|
1128
1568
|
}),
|
|
1129
|
-
|
|
1130
|
-
output:
|
|
1131
|
-
format:
|
|
1132
|
-
size:
|
|
1569
|
+
z12.object({
|
|
1570
|
+
output: z12.string(),
|
|
1571
|
+
format: z12.string(),
|
|
1572
|
+
size: z12.number()
|
|
1133
1573
|
})
|
|
1134
1574
|
]),
|
|
1135
1575
|
handler: async (p, ctx) => {
|
|
@@ -1147,21 +1587,21 @@ var screenshotCommand = registerCommand({
|
|
|
1147
1587
|
if (p.output) {
|
|
1148
1588
|
const dirErr = ensureParentDir(p.output);
|
|
1149
1589
|
if (dirErr) {
|
|
1150
|
-
return
|
|
1590
|
+
return fail5(`Cannot create directory for --output "${p.output}": ${dirErr}`);
|
|
1151
1591
|
}
|
|
1152
1592
|
try {
|
|
1153
1593
|
writeFileSync(p.output, buffer, "binary");
|
|
1154
1594
|
} catch (err) {
|
|
1155
|
-
return
|
|
1595
|
+
return fail5(`Failed to write screenshot to "${p.output}": ${err instanceof Error ? err.message : String(err)}`);
|
|
1156
1596
|
}
|
|
1157
|
-
return
|
|
1597
|
+
return ok12({
|
|
1158
1598
|
output: p.output,
|
|
1159
1599
|
format,
|
|
1160
1600
|
size: buffer.length
|
|
1161
1601
|
});
|
|
1162
1602
|
}
|
|
1163
1603
|
if (p.base64) {
|
|
1164
|
-
return
|
|
1604
|
+
return ok12({
|
|
1165
1605
|
data: buffer.toString("base64"),
|
|
1166
1606
|
format,
|
|
1167
1607
|
size: buffer.length
|
|
@@ -1170,7 +1610,7 @@ var screenshotCommand = registerCommand({
|
|
|
1170
1610
|
ensureScreenshotsDir();
|
|
1171
1611
|
const screenshotPath = generateScreenshotPath(format);
|
|
1172
1612
|
writeFileSync(screenshotPath, buffer, "binary");
|
|
1173
|
-
return
|
|
1613
|
+
return ok12({
|
|
1174
1614
|
output: screenshotPath,
|
|
1175
1615
|
format,
|
|
1176
1616
|
size: buffer.length
|
|
@@ -1179,19 +1619,19 @@ var screenshotCommand = registerCommand({
|
|
|
1179
1619
|
});
|
|
1180
1620
|
|
|
1181
1621
|
// src/commands/structure.ts
|
|
1182
|
-
import { z as
|
|
1183
|
-
import { ok as
|
|
1622
|
+
import { z as z13 } from "zod";
|
|
1623
|
+
import { ok as ok13 } from "@dyyz1993/xcli-core";
|
|
1184
1624
|
var structureCommand = registerCommand({
|
|
1185
1625
|
name: "structure",
|
|
1186
1626
|
description: "Get the DOM structure of the page or an element",
|
|
1187
1627
|
scope: "page",
|
|
1188
1628
|
selectorParams: ["selector"],
|
|
1189
|
-
parameters:
|
|
1190
|
-
selector:
|
|
1191
|
-
depth:
|
|
1629
|
+
parameters: z13.object({
|
|
1630
|
+
selector: z13.string().optional(),
|
|
1631
|
+
depth: z13.number().optional()
|
|
1192
1632
|
}),
|
|
1193
|
-
result:
|
|
1194
|
-
structure:
|
|
1633
|
+
result: z13.object({
|
|
1634
|
+
structure: z13.record(z13.unknown())
|
|
1195
1635
|
}),
|
|
1196
1636
|
handler: async (p, ctx) => {
|
|
1197
1637
|
const structure = await ctx.page.evaluate(
|
|
@@ -1223,30 +1663,30 @@ var structureCommand = registerCommand({
|
|
|
1223
1663
|
},
|
|
1224
1664
|
{ sel: p.selector || "body", maxDepth: p.depth || 5 }
|
|
1225
1665
|
);
|
|
1226
|
-
return
|
|
1666
|
+
return ok13({ structure });
|
|
1227
1667
|
}
|
|
1228
1668
|
});
|
|
1229
1669
|
|
|
1230
1670
|
// src/commands/viewport.ts
|
|
1231
|
-
import { z as
|
|
1232
|
-
import { ok as
|
|
1671
|
+
import { z as z14 } from "zod";
|
|
1672
|
+
import { ok as ok14 } from "@dyyz1993/xcli-core";
|
|
1233
1673
|
var setViewportCommand = registerCommand({
|
|
1234
1674
|
name: "set-viewport",
|
|
1235
1675
|
description: "Set the viewport size and properties",
|
|
1236
1676
|
scope: "browser",
|
|
1237
|
-
parameters:
|
|
1238
|
-
width:
|
|
1239
|
-
height:
|
|
1240
|
-
deviceScaleFactor:
|
|
1241
|
-
isMobile:
|
|
1242
|
-
hasTouch:
|
|
1677
|
+
parameters: z14.object({
|
|
1678
|
+
width: z14.coerce.number(),
|
|
1679
|
+
height: z14.coerce.number(),
|
|
1680
|
+
deviceScaleFactor: z14.coerce.number().optional(),
|
|
1681
|
+
isMobile: z14.boolean().optional(),
|
|
1682
|
+
hasTouch: z14.boolean().optional()
|
|
1243
1683
|
}),
|
|
1244
|
-
result:
|
|
1245
|
-
width:
|
|
1246
|
-
height:
|
|
1247
|
-
deviceScaleFactor:
|
|
1248
|
-
isMobile:
|
|
1249
|
-
hasTouch:
|
|
1684
|
+
result: z14.object({
|
|
1685
|
+
width: z14.number(),
|
|
1686
|
+
height: z14.number(),
|
|
1687
|
+
deviceScaleFactor: z14.number().optional(),
|
|
1688
|
+
isMobile: z14.boolean().optional(),
|
|
1689
|
+
hasTouch: z14.boolean().optional()
|
|
1250
1690
|
}),
|
|
1251
1691
|
handler: async (p, ctx) => {
|
|
1252
1692
|
const viewport = ctx.page.viewportSize();
|
|
@@ -1259,7 +1699,7 @@ var setViewportCommand = registerCommand({
|
|
|
1259
1699
|
...p.isMobile !== void 0 && { isMobile: p.isMobile },
|
|
1260
1700
|
...p.hasTouch !== void 0 && { hasTouch: p.hasTouch }
|
|
1261
1701
|
});
|
|
1262
|
-
return
|
|
1702
|
+
return ok14({
|
|
1263
1703
|
width,
|
|
1264
1704
|
height,
|
|
1265
1705
|
...p.deviceScaleFactor !== void 0 && { deviceScaleFactor: p.deviceScaleFactor },
|
|
@@ -1270,17 +1710,17 @@ var setViewportCommand = registerCommand({
|
|
|
1270
1710
|
});
|
|
1271
1711
|
|
|
1272
1712
|
// src/commands/frame.ts
|
|
1273
|
-
import { z as
|
|
1274
|
-
import { ok as
|
|
1713
|
+
import { z as z15 } from "zod";
|
|
1714
|
+
import { ok as ok15, fail as fail6 } from "@dyyz1993/xcli-core";
|
|
1275
1715
|
var framesCommand = registerCommand({
|
|
1276
1716
|
name: "frames",
|
|
1277
1717
|
description: "List all frames in the current page",
|
|
1278
1718
|
scope: "page",
|
|
1279
|
-
result:
|
|
1280
|
-
frames:
|
|
1281
|
-
index:
|
|
1282
|
-
name:
|
|
1283
|
-
url:
|
|
1719
|
+
result: z15.object({
|
|
1720
|
+
frames: z15.array(z15.object({
|
|
1721
|
+
index: z15.number(),
|
|
1722
|
+
name: z15.string().nullable(),
|
|
1723
|
+
url: z15.string()
|
|
1284
1724
|
}))
|
|
1285
1725
|
}),
|
|
1286
1726
|
handler: async (_p, ctx) => {
|
|
@@ -1291,21 +1731,21 @@ var framesCommand = registerCommand({
|
|
|
1291
1731
|
name: frame.name(),
|
|
1292
1732
|
url: frame.url()
|
|
1293
1733
|
}));
|
|
1294
|
-
return
|
|
1734
|
+
return ok15({ frames: frameList });
|
|
1295
1735
|
}
|
|
1296
1736
|
});
|
|
1297
1737
|
var frameCommand = registerCommand({
|
|
1298
1738
|
name: "frame",
|
|
1299
1739
|
description: "Get frame info by index or name",
|
|
1300
1740
|
scope: "page",
|
|
1301
|
-
parameters:
|
|
1302
|
-
index:
|
|
1303
|
-
name:
|
|
1741
|
+
parameters: z15.object({
|
|
1742
|
+
index: z15.number().int().min(0).optional(),
|
|
1743
|
+
name: z15.string().optional()
|
|
1304
1744
|
}),
|
|
1305
|
-
result:
|
|
1306
|
-
name:
|
|
1307
|
-
url:
|
|
1308
|
-
error:
|
|
1745
|
+
result: z15.object({
|
|
1746
|
+
name: z15.string().nullable(),
|
|
1747
|
+
url: z15.string(),
|
|
1748
|
+
error: z15.string().optional()
|
|
1309
1749
|
}),
|
|
1310
1750
|
handler: async (p, ctx) => {
|
|
1311
1751
|
const discover = ctx.page.discoverFrames;
|
|
@@ -1316,12 +1756,12 @@ var frameCommand = registerCommand({
|
|
|
1316
1756
|
} else if (p.name !== void 0) {
|
|
1317
1757
|
targetFrame = rawFrames.find((f) => f.name() === p.name);
|
|
1318
1758
|
} else {
|
|
1319
|
-
return
|
|
1759
|
+
return fail6("Must provide index or name");
|
|
1320
1760
|
}
|
|
1321
1761
|
if (!targetFrame) {
|
|
1322
|
-
return
|
|
1762
|
+
return fail6("Frame not found");
|
|
1323
1763
|
}
|
|
1324
|
-
return
|
|
1764
|
+
return ok15({
|
|
1325
1765
|
name: targetFrame.name(),
|
|
1326
1766
|
url: targetFrame.url()
|
|
1327
1767
|
});
|
|
@@ -1329,27 +1769,27 @@ var frameCommand = registerCommand({
|
|
|
1329
1769
|
});
|
|
1330
1770
|
|
|
1331
1771
|
// src/commands/ui-debug.ts
|
|
1332
|
-
import { z as
|
|
1333
|
-
import { ok as
|
|
1772
|
+
import { z as z16 } from "zod";
|
|
1773
|
+
import { ok as ok16 } from "@dyyz1993/xcli-core";
|
|
1334
1774
|
var consoleCheckCommand = registerCommand({
|
|
1335
1775
|
name: "console",
|
|
1336
1776
|
description: "Collect and analyze browser console messages (errors, warnings, logs)",
|
|
1337
1777
|
scope: "page",
|
|
1338
|
-
parameters:
|
|
1339
|
-
url:
|
|
1340
|
-
duration:
|
|
1341
|
-
filter:
|
|
1342
|
-
includeStackTraces:
|
|
1778
|
+
parameters: z16.object({
|
|
1779
|
+
url: z16.string().optional().describe("URL to navigate first (optional, uses current page if omitted)"),
|
|
1780
|
+
duration: z16.number().optional().default(5e3).describe("How long to collect messages (ms)"),
|
|
1781
|
+
filter: z16.enum(["all", "error", "warning", "info", "log"]).optional().default("all"),
|
|
1782
|
+
includeStackTraces: z16.boolean().optional().default(true)
|
|
1343
1783
|
}),
|
|
1344
|
-
result:
|
|
1345
|
-
url:
|
|
1346
|
-
duration:
|
|
1347
|
-
total:
|
|
1348
|
-
errors:
|
|
1349
|
-
warnings:
|
|
1350
|
-
messages:
|
|
1351
|
-
summary:
|
|
1352
|
-
passed:
|
|
1784
|
+
result: z16.object({
|
|
1785
|
+
url: z16.string(),
|
|
1786
|
+
duration: z16.number(),
|
|
1787
|
+
total: z16.number(),
|
|
1788
|
+
errors: z16.number(),
|
|
1789
|
+
warnings: z16.number(),
|
|
1790
|
+
messages: z16.array(z16.record(z16.unknown())),
|
|
1791
|
+
summary: z16.string(),
|
|
1792
|
+
passed: z16.boolean()
|
|
1353
1793
|
}),
|
|
1354
1794
|
handler: async (p, ctx) => {
|
|
1355
1795
|
const { page } = ctx;
|
|
@@ -1431,7 +1871,7 @@ ${a.stack || ""}`;
|
|
|
1431
1871
|
const filtered = p.filter === "all" ? messages : messages.filter((m) => m.type === p.filter);
|
|
1432
1872
|
const errorCount = messages.filter((m) => m.type === "error").length;
|
|
1433
1873
|
const warnCount = messages.filter((m) => m.type === "warning").length;
|
|
1434
|
-
return
|
|
1874
|
+
return ok16({
|
|
1435
1875
|
url: page.url(),
|
|
1436
1876
|
duration: p.duration,
|
|
1437
1877
|
total: messages.length,
|
|
@@ -1447,23 +1887,23 @@ var networkCheckCommand = registerCommand({
|
|
|
1447
1887
|
name: "net-debug",
|
|
1448
1888
|
description: "Monitor and analyze network requests \u2014 capture failed requests, slow responses, status codes",
|
|
1449
1889
|
scope: "page",
|
|
1450
|
-
parameters:
|
|
1451
|
-
url:
|
|
1452
|
-
duration:
|
|
1453
|
-
filter:
|
|
1454
|
-
slowThreshold:
|
|
1890
|
+
parameters: z16.object({
|
|
1891
|
+
url: z16.string().optional().describe("URL to navigate first"),
|
|
1892
|
+
duration: z16.number().optional().default(5e3).describe("How long to monitor (ms)"),
|
|
1893
|
+
filter: z16.enum(["all", "failed", "slow", "error", "xhr", "fetch", "document", "stylesheet", "script", "image"]).optional().default("all"),
|
|
1894
|
+
slowThreshold: z16.number().optional().default(3e3).describe('Threshold for "slow" requests (ms)')
|
|
1455
1895
|
}),
|
|
1456
|
-
result:
|
|
1457
|
-
url:
|
|
1458
|
-
duration:
|
|
1459
|
-
totalRequests:
|
|
1460
|
-
failedRequests:
|
|
1461
|
-
slowRequests:
|
|
1462
|
-
errorRequests:
|
|
1463
|
-
totalSizeKB:
|
|
1464
|
-
requests:
|
|
1465
|
-
summary:
|
|
1466
|
-
passed:
|
|
1896
|
+
result: z16.object({
|
|
1897
|
+
url: z16.string(),
|
|
1898
|
+
duration: z16.number(),
|
|
1899
|
+
totalRequests: z16.number(),
|
|
1900
|
+
failedRequests: z16.number(),
|
|
1901
|
+
slowRequests: z16.number(),
|
|
1902
|
+
errorRequests: z16.number(),
|
|
1903
|
+
totalSizeKB: z16.number(),
|
|
1904
|
+
requests: z16.array(z16.record(z16.unknown())),
|
|
1905
|
+
summary: z16.string(),
|
|
1906
|
+
passed: z16.boolean()
|
|
1467
1907
|
}),
|
|
1468
1908
|
handler: async (p, ctx) => {
|
|
1469
1909
|
const { page } = ctx;
|
|
@@ -1545,7 +1985,7 @@ var networkCheckCommand = registerCommand({
|
|
|
1545
1985
|
const slowCount = requests.filter((r) => r.duration >= p.slowThreshold).length;
|
|
1546
1986
|
const errorCount = requests.filter((r) => r.error).length;
|
|
1547
1987
|
const totalSize = requests.reduce((sum, r) => sum + r.size, 0);
|
|
1548
|
-
return
|
|
1988
|
+
return ok16({
|
|
1549
1989
|
url: page.url(),
|
|
1550
1990
|
duration: p.duration,
|
|
1551
1991
|
totalRequests: requests.length,
|
|
@@ -1567,17 +2007,17 @@ var perfCheckCommand = registerCommand({
|
|
|
1567
2007
|
name: "perf",
|
|
1568
2008
|
description: "Audit page performance metrics \u2014 load time, FCP, LCP, CLS, TTFB, resource sizes",
|
|
1569
2009
|
scope: "page",
|
|
1570
|
-
parameters:
|
|
1571
|
-
url:
|
|
1572
|
-
iterations:
|
|
2010
|
+
parameters: z16.object({
|
|
2011
|
+
url: z16.string().optional().describe("URL to navigate (uses current page if omitted)"),
|
|
2012
|
+
iterations: z16.number().optional().default(1).describe("Number of iterations to average")
|
|
1573
2013
|
}),
|
|
1574
|
-
result:
|
|
1575
|
-
url:
|
|
1576
|
-
iterations:
|
|
1577
|
-
metrics:
|
|
1578
|
-
allIterations:
|
|
1579
|
-
passed:
|
|
1580
|
-
summary:
|
|
2014
|
+
result: z16.object({
|
|
2015
|
+
url: z16.string(),
|
|
2016
|
+
iterations: z16.number(),
|
|
2017
|
+
metrics: z16.record(z16.unknown()),
|
|
2018
|
+
allIterations: z16.array(z16.record(z16.unknown())).optional(),
|
|
2019
|
+
passed: z16.boolean(),
|
|
2020
|
+
summary: z16.string()
|
|
1581
2021
|
}),
|
|
1582
2022
|
handler: async (p, ctx) => {
|
|
1583
2023
|
const { page } = ctx;
|
|
@@ -1639,7 +2079,7 @@ var perfCheckCommand = registerCommand({
|
|
|
1639
2079
|
if (resourceStats) avg.resourceStats = resourceStats;
|
|
1640
2080
|
return avg;
|
|
1641
2081
|
})();
|
|
1642
|
-
return
|
|
2082
|
+
return ok16({
|
|
1643
2083
|
url: page.url(),
|
|
1644
2084
|
iterations: p.iterations,
|
|
1645
2085
|
metrics: avgMetrics,
|
|
@@ -1653,23 +2093,23 @@ var healthCheckCommand = registerCommand({
|
|
|
1653
2093
|
name: "health",
|
|
1654
2094
|
description: "Comprehensive page health check \u2014 broken links, missing images, console errors, SEO issues",
|
|
1655
2095
|
scope: "page",
|
|
1656
|
-
parameters:
|
|
1657
|
-
url:
|
|
1658
|
-
checkLinks:
|
|
1659
|
-
checkImages:
|
|
1660
|
-
checkMeta:
|
|
1661
|
-
maxLinks:
|
|
2096
|
+
parameters: z16.object({
|
|
2097
|
+
url: z16.string().optional().describe("URL to check"),
|
|
2098
|
+
checkLinks: z16.boolean().optional().default(true).describe("Check for broken links"),
|
|
2099
|
+
checkImages: z16.boolean().optional().default(true).describe("Check for missing/broken images"),
|
|
2100
|
+
checkMeta: z16.boolean().optional().default(true).describe("Check SEO meta tags"),
|
|
2101
|
+
maxLinks: z16.number().optional().default(50).describe("Max links to check")
|
|
1662
2102
|
}),
|
|
1663
|
-
result:
|
|
1664
|
-
url:
|
|
1665
|
-
title:
|
|
1666
|
-
passed:
|
|
1667
|
-
totalIssues:
|
|
1668
|
-
errors:
|
|
1669
|
-
warnings:
|
|
1670
|
-
info:
|
|
1671
|
-
issues:
|
|
1672
|
-
summary:
|
|
2103
|
+
result: z16.object({
|
|
2104
|
+
url: z16.string(),
|
|
2105
|
+
title: z16.string(),
|
|
2106
|
+
passed: z16.boolean(),
|
|
2107
|
+
totalIssues: z16.number(),
|
|
2108
|
+
errors: z16.number(),
|
|
2109
|
+
warnings: z16.number(),
|
|
2110
|
+
info: z16.number(),
|
|
2111
|
+
issues: z16.array(z16.record(z16.unknown())),
|
|
2112
|
+
summary: z16.string()
|
|
1673
2113
|
}),
|
|
1674
2114
|
handler: async (p, ctx) => {
|
|
1675
2115
|
const { page } = ctx;
|
|
@@ -1756,7 +2196,7 @@ var healthCheckCommand = registerCommand({
|
|
|
1756
2196
|
}, { checkLinks: p.checkLinks, checkImages: p.checkImages, checkMeta: p.checkMeta, maxLinks: p.maxLinks });
|
|
1757
2197
|
const errors = result.issues.filter((i) => i.severity === "error").length;
|
|
1758
2198
|
const warnings = result.issues.filter((i) => i.severity === "warning").length;
|
|
1759
|
-
return
|
|
2199
|
+
return ok16({
|
|
1760
2200
|
url: result.url,
|
|
1761
2201
|
title: result.title,
|
|
1762
2202
|
passed: errors === 0,
|
|
@@ -1771,68 +2211,66 @@ var healthCheckCommand = registerCommand({
|
|
|
1771
2211
|
});
|
|
1772
2212
|
|
|
1773
2213
|
// src/commands/actions.ts
|
|
1774
|
-
import { z as
|
|
1775
|
-
import { ok as
|
|
2214
|
+
import { z as z17 } from "zod";
|
|
2215
|
+
import { ok as ok17 } from "@dyyz1993/xcli-core";
|
|
1776
2216
|
import { writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
1777
|
-
import { join as
|
|
1778
|
-
import { homedir as homedir3 } from "os";
|
|
1779
|
-
var SCREENSHOTS_DIR2 = join3(homedir3(), ".xbrowser", "screenshots");
|
|
2217
|
+
import { join as join5 } from "path";
|
|
1780
2218
|
function ensureScreenshotsDir2() {
|
|
1781
|
-
mkdirSync2(
|
|
2219
|
+
mkdirSync2(resolveScreenshotsDir(), { recursive: true });
|
|
1782
2220
|
}
|
|
1783
2221
|
function generateScreenshotPath2(format) {
|
|
1784
2222
|
const timestamp = Date.now();
|
|
1785
2223
|
const random = Math.random().toString(36).slice(2, 8);
|
|
1786
2224
|
const ext = format === "jpeg" ? "jpg" : "png";
|
|
1787
|
-
return
|
|
2225
|
+
return join5(resolveScreenshotsDir(), `screenshot-${timestamp}-${random}.${ext}`);
|
|
1788
2226
|
}
|
|
1789
|
-
var waitActionSchema =
|
|
1790
|
-
type:
|
|
1791
|
-
milliseconds:
|
|
1792
|
-
selector:
|
|
2227
|
+
var waitActionSchema = z17.object({
|
|
2228
|
+
type: z17.literal("wait"),
|
|
2229
|
+
milliseconds: z17.number().positive().optional(),
|
|
2230
|
+
selector: z17.string().optional()
|
|
1793
2231
|
}).refine(
|
|
1794
2232
|
(d) => (d.milliseconds !== void 0 || d.selector !== void 0) && !(d.milliseconds !== void 0 && d.selector !== void 0),
|
|
1795
2233
|
{ message: "Either 'milliseconds' or 'selector' must be provided, but not both." }
|
|
1796
2234
|
);
|
|
1797
|
-
var clickActionSchema =
|
|
1798
|
-
type:
|
|
1799
|
-
selector:
|
|
1800
|
-
all:
|
|
2235
|
+
var clickActionSchema = z17.object({
|
|
2236
|
+
type: z17.literal("click"),
|
|
2237
|
+
selector: z17.string(),
|
|
2238
|
+
all: z17.boolean().optional()
|
|
1801
2239
|
});
|
|
1802
|
-
var screenshotActionSchema =
|
|
1803
|
-
type:
|
|
1804
|
-
fullPage:
|
|
1805
|
-
quality:
|
|
1806
|
-
viewport:
|
|
1807
|
-
base64:
|
|
2240
|
+
var screenshotActionSchema = z17.object({
|
|
2241
|
+
type: z17.literal("screenshot"),
|
|
2242
|
+
fullPage: z17.boolean().optional(),
|
|
2243
|
+
quality: z17.number().min(1).max(100).optional(),
|
|
2244
|
+
viewport: z17.object({ width: z17.number().int().positive(), height: z17.number().int().positive() }).optional(),
|
|
2245
|
+
base64: z17.boolean().optional().describe("Return base64 data instead of file path")
|
|
1808
2246
|
});
|
|
1809
|
-
var writeActionSchema =
|
|
1810
|
-
type:
|
|
1811
|
-
text:
|
|
2247
|
+
var writeActionSchema = z17.object({
|
|
2248
|
+
type: z17.literal("write"),
|
|
2249
|
+
text: z17.string()
|
|
1812
2250
|
});
|
|
1813
|
-
var pressActionSchema =
|
|
1814
|
-
type:
|
|
1815
|
-
key:
|
|
2251
|
+
var pressActionSchema = z17.object({
|
|
2252
|
+
type: z17.literal("press"),
|
|
2253
|
+
key: z17.string()
|
|
1816
2254
|
});
|
|
1817
|
-
var scrollActionSchema =
|
|
1818
|
-
type:
|
|
1819
|
-
direction:
|
|
1820
|
-
selector:
|
|
2255
|
+
var scrollActionSchema = z17.object({
|
|
2256
|
+
type: z17.literal("scroll"),
|
|
2257
|
+
direction: z17.enum(["up", "down"]).optional(),
|
|
2258
|
+
selector: z17.string().optional()
|
|
1821
2259
|
});
|
|
1822
|
-
var scrapeActionSchema =
|
|
1823
|
-
type:
|
|
2260
|
+
var scrapeActionSchema = z17.object({
|
|
2261
|
+
type: z17.literal("scrape")
|
|
1824
2262
|
});
|
|
1825
|
-
var executeJavascriptActionSchema =
|
|
1826
|
-
type:
|
|
1827
|
-
script:
|
|
2263
|
+
var executeJavascriptActionSchema = z17.object({
|
|
2264
|
+
type: z17.literal("executeJavascript"),
|
|
2265
|
+
script: z17.string()
|
|
1828
2266
|
});
|
|
1829
|
-
var pdfActionSchema =
|
|
1830
|
-
type:
|
|
1831
|
-
landscape:
|
|
1832
|
-
scale:
|
|
1833
|
-
format:
|
|
2267
|
+
var pdfActionSchema = z17.object({
|
|
2268
|
+
type: z17.literal("pdf"),
|
|
2269
|
+
landscape: z17.boolean().optional(),
|
|
2270
|
+
scale: z17.number().optional(),
|
|
2271
|
+
format: z17.enum(["A0", "A1", "A2", "A3", "A4", "A5", "A6", "Letter", "Legal", "Tabloid", "Ledger"]).optional()
|
|
1834
2272
|
});
|
|
1835
|
-
var actionSchema =
|
|
2273
|
+
var actionSchema = z17.union([
|
|
1836
2274
|
waitActionSchema,
|
|
1837
2275
|
clickActionSchema,
|
|
1838
2276
|
screenshotActionSchema,
|
|
@@ -1923,11 +2361,11 @@ var actionsCommand = registerCommand({
|
|
|
1923
2361
|
name: "actions",
|
|
1924
2362
|
description: "Execute a sequence of actions (wait, click, scroll, screenshot, fill, etc.)",
|
|
1925
2363
|
scope: "page",
|
|
1926
|
-
parameters:
|
|
1927
|
-
url:
|
|
1928
|
-
actions:
|
|
1929
|
-
output:
|
|
1930
|
-
timeout:
|
|
2364
|
+
parameters: z17.object({
|
|
2365
|
+
url: z17.string().describe("Starting URL"),
|
|
2366
|
+
actions: z17.array(actionSchema).max(MAX_ACTIONS).describe("Array of actions (max 50)"),
|
|
2367
|
+
output: z17.enum(["text", "json"]).default("json").describe("Output format: text or json"),
|
|
2368
|
+
timeout: z17.number().default(60).describe("Overall timeout in seconds (default: 60)")
|
|
1931
2369
|
}),
|
|
1932
2370
|
handler: async (p, ctx) => {
|
|
1933
2371
|
await ctx.page.goto(p.url, { waitUntil: "domcontentloaded" });
|
|
@@ -1947,14 +2385,14 @@ var actionsCommand = registerCommand({
|
|
|
1947
2385
|
const finalUrl = ctx.page.url();
|
|
1948
2386
|
const timedOut = results.length < p.actions.length;
|
|
1949
2387
|
if (p.output === "text") {
|
|
1950
|
-
return
|
|
2388
|
+
return ok17({
|
|
1951
2389
|
title,
|
|
1952
2390
|
url: finalUrl,
|
|
1953
2391
|
actions: results.map((r) => JSON.stringify(r)).join("\n"),
|
|
1954
2392
|
...timedOut ? { warning: `Timed out after ${p.timeout}s, completed ${results.length}/${p.actions.length} actions` } : {}
|
|
1955
2393
|
});
|
|
1956
2394
|
}
|
|
1957
|
-
return
|
|
2395
|
+
return ok17({
|
|
1958
2396
|
title,
|
|
1959
2397
|
url: finalUrl,
|
|
1960
2398
|
results,
|
|
@@ -1964,8 +2402,8 @@ var actionsCommand = registerCommand({
|
|
|
1964
2402
|
});
|
|
1965
2403
|
|
|
1966
2404
|
// src/commands/scrape.ts
|
|
1967
|
-
import { z as
|
|
1968
|
-
import { ok as
|
|
2405
|
+
import { z as z18 } from "zod";
|
|
2406
|
+
import { ok as ok18, fail as fail7 } from "@dyyz1993/xcli-core";
|
|
1969
2407
|
|
|
1970
2408
|
// src/lib/html-to-markdown.ts
|
|
1971
2409
|
import * as cheerio from "cheerio";
|
|
@@ -2350,15 +2788,15 @@ var scrapeCommand = registerCommand({
|
|
|
2350
2788
|
description: "Scrape a page and convert to Markdown (with JS rendering)",
|
|
2351
2789
|
scope: "project",
|
|
2352
2790
|
selectorParams: ["selector"],
|
|
2353
|
-
parameters:
|
|
2354
|
-
url:
|
|
2355
|
-
selector:
|
|
2356
|
-
timeout:
|
|
2357
|
-
format:
|
|
2358
|
-
onlyMainContent:
|
|
2359
|
-
retries:
|
|
2360
|
-
waitAfterLoad:
|
|
2361
|
-
mode:
|
|
2791
|
+
parameters: z18.object({
|
|
2792
|
+
url: z18.string().optional(),
|
|
2793
|
+
selector: z18.string().optional(),
|
|
2794
|
+
timeout: z18.number().default(3e4),
|
|
2795
|
+
format: z18.enum(["markdown", "html", "text"]).default("markdown"),
|
|
2796
|
+
onlyMainContent: z18.boolean().default(true),
|
|
2797
|
+
retries: z18.number().int().min(0).max(5).optional().default(2).describe("\u91CD\u8BD5\u6B21\u6570\uFF08\u9ED8\u8BA4 2\uFF09"),
|
|
2798
|
+
waitAfterLoad: z18.number().int().optional().default(0).describe("\u9875\u9762\u52A0\u8F7D\u540E\u989D\u5916\u7B49\u5F85\u6BEB\u79D2"),
|
|
2799
|
+
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")
|
|
2362
2800
|
}),
|
|
2363
2801
|
handler: async (p, ctx) => {
|
|
2364
2802
|
const { context, page } = await createEphemeralContext(resolveLaunchOpts(ctx));
|
|
@@ -2366,7 +2804,7 @@ var scrapeCommand = registerCommand({
|
|
|
2366
2804
|
try {
|
|
2367
2805
|
const targetUrl = p.url || page.url();
|
|
2368
2806
|
if (!targetUrl || targetUrl === "about:blank") {
|
|
2369
|
-
return
|
|
2807
|
+
return fail7("URL is required. Provide a URL or connect to a browser (--cdp) with a non-blank page.");
|
|
2370
2808
|
}
|
|
2371
2809
|
let lastError;
|
|
2372
2810
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
@@ -2436,7 +2874,7 @@ var scrapeCommand = registerCommand({
|
|
|
2436
2874
|
} catch {
|
|
2437
2875
|
}
|
|
2438
2876
|
if (p.mode === "smart") {
|
|
2439
|
-
return
|
|
2877
|
+
return ok18(structured);
|
|
2440
2878
|
}
|
|
2441
2879
|
if (p.mode === "compact") {
|
|
2442
2880
|
const compactData = structured.tables.length > 0 ? structured.tables.map((t) => ({
|
|
@@ -2449,9 +2887,9 @@ var scrapeCommand = registerCommand({
|
|
|
2449
2887
|
return compact;
|
|
2450
2888
|
})
|
|
2451
2889
|
})) : structured.mainText?.substring(0, 500);
|
|
2452
|
-
return
|
|
2890
|
+
return ok18({ url: structured.url, title: structured.title, data: compactData });
|
|
2453
2891
|
}
|
|
2454
|
-
return
|
|
2892
|
+
return ok18(structured);
|
|
2455
2893
|
}
|
|
2456
2894
|
let content;
|
|
2457
2895
|
switch (p.format) {
|
|
@@ -2510,7 +2948,7 @@ var scrapeCommand = registerCommand({
|
|
|
2510
2948
|
content = await page.innerText("body");
|
|
2511
2949
|
break;
|
|
2512
2950
|
}
|
|
2513
|
-
return
|
|
2951
|
+
return ok18({ content, title, url: finalUrl });
|
|
2514
2952
|
} catch (err) {
|
|
2515
2953
|
lastError = err instanceof Error ? err : new Error(String(err));
|
|
2516
2954
|
if (attempt < maxAttempts) {
|
|
@@ -2520,20 +2958,20 @@ var scrapeCommand = registerCommand({
|
|
|
2520
2958
|
}
|
|
2521
2959
|
}
|
|
2522
2960
|
}
|
|
2523
|
-
return
|
|
2961
|
+
return fail7(`Scrape failed after ${maxAttempts} attempt(s): ${lastError?.message ?? "unknown error"}`);
|
|
2524
2962
|
} finally {
|
|
2525
2963
|
await closeEphemeralContext(context);
|
|
2526
2964
|
}
|
|
2527
2965
|
},
|
|
2528
|
-
result:
|
|
2529
|
-
url:
|
|
2530
|
-
title:
|
|
2966
|
+
result: z18.object({
|
|
2967
|
+
url: z18.string(),
|
|
2968
|
+
title: z18.string()
|
|
2531
2969
|
}).passthrough()
|
|
2532
2970
|
});
|
|
2533
2971
|
|
|
2534
2972
|
// src/commands/map.ts
|
|
2535
|
-
import { z as
|
|
2536
|
-
import { ok as
|
|
2973
|
+
import { z as z19 } from "zod";
|
|
2974
|
+
import { ok as ok19, fail as fail8 } from "@dyyz1993/xcli-core";
|
|
2537
2975
|
|
|
2538
2976
|
// src/utils/url.ts
|
|
2539
2977
|
var SKIP_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
@@ -2768,21 +3206,21 @@ var mapCommand = registerCommand({
|
|
|
2768
3206
|
name: "map",
|
|
2769
3207
|
description: "Discover all URLs on a website via sitemap and page link extraction",
|
|
2770
3208
|
scope: "project",
|
|
2771
|
-
parameters:
|
|
2772
|
-
url:
|
|
2773
|
-
search:
|
|
2774
|
-
sitemap:
|
|
2775
|
-
includeSubdomains:
|
|
2776
|
-
allowExternalLinks:
|
|
2777
|
-
limit:
|
|
2778
|
-
verbose:
|
|
3209
|
+
parameters: z19.object({
|
|
3210
|
+
url: z19.string().optional(),
|
|
3211
|
+
search: z19.string().optional(),
|
|
3212
|
+
sitemap: z19.enum(["include", "only"]).optional(),
|
|
3213
|
+
includeSubdomains: z19.boolean().optional(),
|
|
3214
|
+
allowExternalLinks: z19.boolean().optional().describe("Include links to external domains"),
|
|
3215
|
+
limit: z19.number().optional(),
|
|
3216
|
+
verbose: z19.boolean().default(false).describe("Show progress feedback")
|
|
2779
3217
|
}),
|
|
2780
3218
|
handler: async (p, ctx) => {
|
|
2781
3219
|
const { context, page } = await createEphemeralContext(resolveLaunchOpts(ctx));
|
|
2782
3220
|
try {
|
|
2783
3221
|
const targetUrl = p.url || page.url();
|
|
2784
3222
|
if (!targetUrl || targetUrl === "about:blank") {
|
|
2785
|
-
return
|
|
3223
|
+
return fail8("URL is required. Provide a URL or connect to a browser (--cdp) with a non-blank page.");
|
|
2786
3224
|
}
|
|
2787
3225
|
const links = await discoverUrls(page, targetUrl, {
|
|
2788
3226
|
sitemap: p.sitemap,
|
|
@@ -2793,7 +3231,7 @@ var mapCommand = registerCommand({
|
|
|
2793
3231
|
verbose: p.verbose
|
|
2794
3232
|
});
|
|
2795
3233
|
const linkObjects = links.map((url) => ({ url }));
|
|
2796
|
-
return
|
|
3234
|
+
return ok19({
|
|
2797
3235
|
links: linkObjects,
|
|
2798
3236
|
success: true
|
|
2799
3237
|
});
|
|
@@ -2801,15 +3239,15 @@ var mapCommand = registerCommand({
|
|
|
2801
3239
|
await closeEphemeralContext(context);
|
|
2802
3240
|
}
|
|
2803
3241
|
},
|
|
2804
|
-
result:
|
|
2805
|
-
links:
|
|
2806
|
-
success:
|
|
3242
|
+
result: z19.object({
|
|
3243
|
+
links: z19.array(z19.object({ url: z19.string() })),
|
|
3244
|
+
success: z19.boolean()
|
|
2807
3245
|
})
|
|
2808
3246
|
});
|
|
2809
3247
|
|
|
2810
3248
|
// src/commands/crawl.ts
|
|
2811
|
-
import { z as
|
|
2812
|
-
import { ok as
|
|
3249
|
+
import { z as z20 } from "zod";
|
|
3250
|
+
import { ok as ok20 } from "@dyyz1993/xcli-core";
|
|
2813
3251
|
function stripHashAnchorQuery(url) {
|
|
2814
3252
|
try {
|
|
2815
3253
|
const parsed = new URL(url);
|
|
@@ -2986,21 +3424,21 @@ var crawlCommand = registerCommand({
|
|
|
2986
3424
|
name: "crawl",
|
|
2987
3425
|
description: "Crawl a website and extract content from all pages",
|
|
2988
3426
|
scope: "project",
|
|
2989
|
-
parameters:
|
|
2990
|
-
url:
|
|
2991
|
-
limit:
|
|
2992
|
-
maxDepth:
|
|
2993
|
-
includePaths:
|
|
2994
|
-
excludePaths:
|
|
2995
|
-
allowSubdomains:
|
|
2996
|
-
allowExternalLinks:
|
|
2997
|
-
allowBackwardCrawling:
|
|
2998
|
-
enableSpa:
|
|
2999
|
-
format:
|
|
3000
|
-
onlyMainContent:
|
|
3001
|
-
concurrency:
|
|
3002
|
-
retries:
|
|
3003
|
-
verbose:
|
|
3427
|
+
parameters: z20.object({
|
|
3428
|
+
url: z20.string(),
|
|
3429
|
+
limit: z20.number().default(10),
|
|
3430
|
+
maxDepth: z20.number().default(3),
|
|
3431
|
+
includePaths: z20.array(z20.string()).optional(),
|
|
3432
|
+
excludePaths: z20.array(z20.string()).optional(),
|
|
3433
|
+
allowSubdomains: z20.boolean().default(false),
|
|
3434
|
+
allowExternalLinks: z20.boolean().default(false),
|
|
3435
|
+
allowBackwardCrawling: z20.boolean().default(false),
|
|
3436
|
+
enableSpa: z20.boolean().default(true).describe("Disable to skip SPA route detection"),
|
|
3437
|
+
format: z20.enum(["markdown", "html"]).default("markdown"),
|
|
3438
|
+
onlyMainContent: z20.boolean().default(true),
|
|
3439
|
+
concurrency: z20.number().default(3),
|
|
3440
|
+
retries: z20.number().default(2),
|
|
3441
|
+
verbose: z20.boolean().default(false)
|
|
3004
3442
|
}),
|
|
3005
3443
|
handler: async (p, ctx) => {
|
|
3006
3444
|
const startUrl = new URL(p.url);
|
|
@@ -3139,7 +3577,7 @@ var crawlCommand = registerCommand({
|
|
|
3139
3577
|
if (errorPages.length > 0) {
|
|
3140
3578
|
response.errors = errorPages;
|
|
3141
3579
|
}
|
|
3142
|
-
return
|
|
3580
|
+
return ok20(response);
|
|
3143
3581
|
} finally {
|
|
3144
3582
|
for (const ctx2 of contexts) {
|
|
3145
3583
|
await ctx2.close().catch(() => {
|
|
@@ -3149,15 +3587,15 @@ var crawlCommand = registerCommand({
|
|
|
3149
3587
|
} catch (err) {
|
|
3150
3588
|
const message = err instanceof Error ? err.message : String(err);
|
|
3151
3589
|
const successPages = results.filter((r) => !isPageError(r));
|
|
3152
|
-
return
|
|
3590
|
+
return ok20({ pages: successPages, total: successPages.length, success: false, error: message });
|
|
3153
3591
|
}
|
|
3154
3592
|
}
|
|
3155
3593
|
});
|
|
3156
3594
|
|
|
3157
3595
|
// src/commands/search.ts
|
|
3158
|
-
import { z as
|
|
3596
|
+
import { z as z21 } from "zod";
|
|
3159
3597
|
import * as cheerio2 from "cheerio";
|
|
3160
|
-
import { ok as
|
|
3598
|
+
import { ok as ok21 } from "@dyyz1993/xcli-core";
|
|
3161
3599
|
function getRecencyParams(recency) {
|
|
3162
3600
|
const now = Math.floor(Date.now() / 1e3);
|
|
3163
3601
|
switch (recency) {
|
|
@@ -3491,16 +3929,16 @@ var searchCommand = registerCommand({
|
|
|
3491
3929
|
name: "search",
|
|
3492
3930
|
description: "Search the web and extract results with engine fallback",
|
|
3493
3931
|
scope: "project",
|
|
3494
|
-
parameters:
|
|
3495
|
-
query:
|
|
3496
|
-
engine:
|
|
3497
|
-
limit:
|
|
3498
|
-
full:
|
|
3499
|
-
format:
|
|
3500
|
-
timeout:
|
|
3501
|
-
recency:
|
|
3502
|
-
fallback:
|
|
3503
|
-
site:
|
|
3932
|
+
parameters: z21.object({
|
|
3933
|
+
query: z21.string(),
|
|
3934
|
+
engine: z21.string().optional(),
|
|
3935
|
+
limit: z21.number().default(10),
|
|
3936
|
+
full: z21.boolean().default(false),
|
|
3937
|
+
format: z21.enum(["markdown", "json", "text"]).default("markdown"),
|
|
3938
|
+
timeout: z21.number().default(15e3),
|
|
3939
|
+
recency: z21.enum(["hour", "day", "week", "month", "year"]).optional().describe("Filter by time: hour/day/week/month/year"),
|
|
3940
|
+
fallback: z21.boolean().default(false).describe("Sequential engine fallback instead of parallel"),
|
|
3941
|
+
site: z21.string().optional().describe("Limit results to a specific site (e.g. github.com, v2ex.com)")
|
|
3504
3942
|
}),
|
|
3505
3943
|
handler: async (p, ctx) => {
|
|
3506
3944
|
const { context } = await createEphemeralContext(resolveLaunchOpts(ctx));
|
|
@@ -3606,7 +4044,7 @@ ${errors.map((e) => ` - ${e.engine}: ${e.error}`).join("\n")}`
|
|
|
3606
4044
|
lines.push(`> ${r.snippet}`);
|
|
3607
4045
|
lines.push("");
|
|
3608
4046
|
}
|
|
3609
|
-
return
|
|
4047
|
+
return ok21({ ...searchResult, content: lines.join("\n") });
|
|
3610
4048
|
}
|
|
3611
4049
|
if (p.format === "text") {
|
|
3612
4050
|
const lines = [`Search: ${searchResult.query} (Engine: ${searchResult.engine}, Total: ${searchResult.total})`, ""];
|
|
@@ -3616,9 +4054,9 @@ ${errors.map((e) => ` - ${e.engine}: ${e.error}`).join("\n")}`
|
|
|
3616
4054
|
lines.push(` ${r.snippet}`);
|
|
3617
4055
|
lines.push("");
|
|
3618
4056
|
}
|
|
3619
|
-
return
|
|
4057
|
+
return ok21({ ...searchResult, content: lines.join("\n") });
|
|
3620
4058
|
}
|
|
3621
|
-
return
|
|
4059
|
+
return ok21(searchResult);
|
|
3622
4060
|
} finally {
|
|
3623
4061
|
await closeEphemeralContext(context);
|
|
3624
4062
|
}
|
|
@@ -3626,8 +4064,8 @@ ${errors.map((e) => ` - ${e.engine}: ${e.error}`).join("\n")}`
|
|
|
3626
4064
|
});
|
|
3627
4065
|
|
|
3628
4066
|
// src/commands/network.ts
|
|
3629
|
-
import { z as
|
|
3630
|
-
import { ok as
|
|
4067
|
+
import { z as z22 } from "zod";
|
|
4068
|
+
import { ok as ok22, fail as fail9 } from "@dyyz1993/xcli-core";
|
|
3631
4069
|
function extractPath2(url) {
|
|
3632
4070
|
try {
|
|
3633
4071
|
const u = new URL(url);
|
|
@@ -3725,19 +4163,19 @@ var networkCommand = registerCommand({
|
|
|
3725
4163
|
name: "network",
|
|
3726
4164
|
description: "Capture and filter network responses from a URL",
|
|
3727
4165
|
scope: "project",
|
|
3728
|
-
parameters:
|
|
3729
|
-
url:
|
|
3730
|
-
filter:
|
|
3731
|
-
match:
|
|
3732
|
-
search:
|
|
3733
|
-
console:
|
|
3734
|
-
timeout:
|
|
3735
|
-
wait:
|
|
3736
|
-
limit:
|
|
3737
|
-
format:
|
|
3738
|
-
ws:
|
|
3739
|
-
listen:
|
|
3740
|
-
duration:
|
|
4166
|
+
parameters: z22.object({
|
|
4167
|
+
url: z22.string(),
|
|
4168
|
+
filter: z22.string().optional(),
|
|
4169
|
+
match: z22.string().optional(),
|
|
4170
|
+
search: z22.string().optional().describe("Search within all captured response bodies"),
|
|
4171
|
+
console: z22.boolean().default(false),
|
|
4172
|
+
timeout: z22.number().default(3e4),
|
|
4173
|
+
wait: z22.number().default(3e3),
|
|
4174
|
+
limit: z22.number().default(50),
|
|
4175
|
+
format: z22.enum(["summary", "json"]).default("summary"),
|
|
4176
|
+
ws: z22.boolean().optional().default(false).describe("Only show WebSocket messages"),
|
|
4177
|
+
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"),
|
|
4178
|
+
duration: z22.number().int().optional().default(15e3).describe("\u76D1\u542C\u6A21\u5F0F\u7B49\u5F85\u65F6\u957F\uFF08\u6BEB\u79D2\uFF0C\u9ED8\u8BA4 15000\uFF09")
|
|
3741
4179
|
}),
|
|
3742
4180
|
handler: async (p, ctx) => {
|
|
3743
4181
|
const startTime = Date.now();
|
|
@@ -3792,7 +4230,7 @@ var networkCommand = registerCommand({
|
|
|
3792
4230
|
};
|
|
3793
4231
|
if (p.listen) {
|
|
3794
4232
|
const page2 = ctx.page;
|
|
3795
|
-
if (!page2) return
|
|
4233
|
+
if (!page2) return fail9("No active page. Use --cdp to connect first.");
|
|
3796
4234
|
const captures = [];
|
|
3797
4235
|
const consoleMessages = [];
|
|
3798
4236
|
const wsCaptures = [];
|
|
@@ -3819,7 +4257,7 @@ var networkCommand = registerCommand({
|
|
|
3819
4257
|
page2.off("response", handler);
|
|
3820
4258
|
const duration = Date.now() - startTime;
|
|
3821
4259
|
if (p.ws && wsCaptures.length > 0) {
|
|
3822
|
-
return
|
|
4260
|
+
return ok22({
|
|
3823
4261
|
url: "[listen mode]",
|
|
3824
4262
|
duration,
|
|
3825
4263
|
total: captures.length,
|
|
@@ -3833,9 +4271,9 @@ var networkCommand = registerCommand({
|
|
|
3833
4271
|
});
|
|
3834
4272
|
}
|
|
3835
4273
|
if (p.format === "json") {
|
|
3836
|
-
return
|
|
4274
|
+
return ok22(buildJsonOutput("[listen mode]", captures, consoleMessages, captures.length, wsCaptures));
|
|
3837
4275
|
}
|
|
3838
|
-
return
|
|
4276
|
+
return ok22(buildSummaryOutput("[listen mode]", duration, captures, consoleMessages, captures.length, wsCaptures));
|
|
3839
4277
|
}
|
|
3840
4278
|
const { context, page } = await createEphemeralContext(resolveLaunchOpts(ctx));
|
|
3841
4279
|
try {
|
|
@@ -3890,7 +4328,7 @@ var networkCommand = registerCommand({
|
|
|
3890
4328
|
}
|
|
3891
4329
|
const duration = Date.now() - startTime;
|
|
3892
4330
|
if (p.ws && wsCaptures.length > 0) {
|
|
3893
|
-
return
|
|
4331
|
+
return ok22({
|
|
3894
4332
|
url: p.url,
|
|
3895
4333
|
duration,
|
|
3896
4334
|
total: captures.length,
|
|
@@ -3904,9 +4342,9 @@ var networkCommand = registerCommand({
|
|
|
3904
4342
|
});
|
|
3905
4343
|
}
|
|
3906
4344
|
if (p.format === "json") {
|
|
3907
|
-
return
|
|
4345
|
+
return ok22(buildJsonOutput(p.url, results, consoleMessages, totalCount, wsCaptures, searchResults));
|
|
3908
4346
|
}
|
|
3909
|
-
return
|
|
4347
|
+
return ok22(buildSummaryOutput(p.url, duration, results, consoleMessages, totalCount, wsCaptures));
|
|
3910
4348
|
} finally {
|
|
3911
4349
|
await closeEphemeralContext(context);
|
|
3912
4350
|
}
|
|
@@ -3914,7 +4352,7 @@ var networkCommand = registerCommand({
|
|
|
3914
4352
|
});
|
|
3915
4353
|
|
|
3916
4354
|
// src/commands/ai-search-engines.ts
|
|
3917
|
-
import { z as
|
|
4355
|
+
import { z as z23 } from "zod";
|
|
3918
4356
|
var ENGINE_CONFIGS = {
|
|
3919
4357
|
deepseek: {
|
|
3920
4358
|
key: "deepseek",
|
|
@@ -4168,11 +4606,11 @@ var ENGINE_CONFIGS = {
|
|
|
4168
4606
|
}
|
|
4169
4607
|
};
|
|
4170
4608
|
var ALL_ENGINE_KEYS = Object.keys(ENGINE_CONFIGS);
|
|
4171
|
-
var ENGINE_KEY_ENUM =
|
|
4609
|
+
var ENGINE_KEY_ENUM = z23.enum(ALL_ENGINE_KEYS);
|
|
4172
4610
|
|
|
4173
4611
|
// src/commands/snapshot.ts
|
|
4174
|
-
import { z as
|
|
4175
|
-
import { ok as
|
|
4612
|
+
import { z as z24 } from "zod";
|
|
4613
|
+
import { ok as ok23, fail as fail10, normalizeTips as normalizeTips3 } from "@dyyz1993/xcli-core";
|
|
4176
4614
|
|
|
4177
4615
|
// src/runtime/ref-store.ts
|
|
4178
4616
|
var sessions = /* @__PURE__ */ new Map();
|
|
@@ -4413,8 +4851,8 @@ async function resolveRefParams(page, params, selectorKeys, cache, sessionId) {
|
|
|
4413
4851
|
}
|
|
4414
4852
|
|
|
4415
4853
|
// src/utils/site-semantics.ts
|
|
4416
|
-
import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync, readFileSync } from "fs";
|
|
4417
|
-
import { join as
|
|
4854
|
+
import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
4855
|
+
import { join as join6, dirname as dirname2 } from "path";
|
|
4418
4856
|
import { homedir as homedir4 } from "os";
|
|
4419
4857
|
import { stringify, parse } from "yaml";
|
|
4420
4858
|
import { execFile } from "child_process";
|
|
@@ -4492,10 +4930,10 @@ function inferAction(role, label) {
|
|
|
4492
4930
|
return {};
|
|
4493
4931
|
}
|
|
4494
4932
|
function getSemanticsDir() {
|
|
4495
|
-
return
|
|
4933
|
+
return join6(homedir4(), ".xbrowser", "site-semantics");
|
|
4496
4934
|
}
|
|
4497
4935
|
function getSemanticsPath(domain) {
|
|
4498
|
-
return
|
|
4936
|
+
return join6(getSemanticsDir(), `${domain}.yaml`);
|
|
4499
4937
|
}
|
|
4500
4938
|
function extractDomain2(url) {
|
|
4501
4939
|
try {
|
|
@@ -4509,9 +4947,9 @@ function saveSemantics(domain, pagePath, url, elements) {
|
|
|
4509
4947
|
const filePath = getSemanticsPath(domain);
|
|
4510
4948
|
const dir = dirname2(filePath);
|
|
4511
4949
|
let site;
|
|
4512
|
-
if (
|
|
4950
|
+
if (existsSync3(filePath)) {
|
|
4513
4951
|
try {
|
|
4514
|
-
site = parse(
|
|
4952
|
+
site = parse(readFileSync3(filePath, "utf-8"));
|
|
4515
4953
|
} catch {
|
|
4516
4954
|
site = { site: domain, pages: {} };
|
|
4517
4955
|
}
|
|
@@ -4531,16 +4969,16 @@ function saveSemantics(domain, pagePath, url, elements) {
|
|
|
4531
4969
|
}
|
|
4532
4970
|
}
|
|
4533
4971
|
site.updated_at = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
4534
|
-
if (!
|
|
4972
|
+
if (!existsSync3(dir)) {
|
|
4535
4973
|
mkdirSync3(dir, { recursive: true });
|
|
4536
4974
|
}
|
|
4537
4975
|
writeFileSync3(filePath, stringify(site, { lineWidth: 0 }));
|
|
4538
4976
|
}
|
|
4539
4977
|
function loadSemantics(domain) {
|
|
4540
4978
|
const filePath = getSemanticsPath(domain);
|
|
4541
|
-
if (!
|
|
4979
|
+
if (!existsSync3(filePath)) return null;
|
|
4542
4980
|
try {
|
|
4543
|
-
return parse(
|
|
4981
|
+
return parse(readFileSync3(filePath, "utf-8"));
|
|
4544
4982
|
} catch {
|
|
4545
4983
|
return null;
|
|
4546
4984
|
}
|
|
@@ -5084,24 +5522,24 @@ var snapshotCommand = registerCommand({
|
|
|
5084
5522
|
description: "Capture a quick page state snapshot \u2014 aria tree, visible text, or DOM summary",
|
|
5085
5523
|
scope: "page",
|
|
5086
5524
|
selectorParams: ["selector"],
|
|
5087
|
-
parameters:
|
|
5088
|
-
type:
|
|
5089
|
-
selector:
|
|
5090
|
-
depth:
|
|
5091
|
-
interactive:
|
|
5092
|
-
interactiveOnly:
|
|
5093
|
-
i:
|
|
5094
|
-
compact:
|
|
5095
|
-
c:
|
|
5096
|
-
selectors:
|
|
5097
|
-
all:
|
|
5525
|
+
parameters: z24.object({
|
|
5526
|
+
type: z24.enum(["aria", "text", "dom", "all"]).default("aria").describe("Snapshot type: aria (accessibility tree), text (visible text), dom (element summary), all (combined)"),
|
|
5527
|
+
selector: z24.string().optional().describe("Scope to a specific element"),
|
|
5528
|
+
depth: z24.number().optional().default(6).describe("Max depth for DOM/aria tree"),
|
|
5529
|
+
interactive: z24.boolean().optional().default(false).describe("Return interactive agent refs only"),
|
|
5530
|
+
interactiveOnly: z24.boolean().optional().default(false).describe("Alias for interactive"),
|
|
5531
|
+
i: z24.boolean().optional().default(false).describe("Short alias for interactive"),
|
|
5532
|
+
compact: z24.boolean().optional().default(false).describe("Include compact xbrowser style snapshot text"),
|
|
5533
|
+
c: z24.boolean().optional().default(false).describe("Short alias for compact"),
|
|
5534
|
+
selectors: z24.boolean().optional().default(false).describe("Include ref to CSS selector map"),
|
|
5535
|
+
all: z24.boolean().optional().default(false).describe("Include hidden interactive targets when using interactive snapshot")
|
|
5098
5536
|
}),
|
|
5099
|
-
result:
|
|
5100
|
-
url:
|
|
5101
|
-
title:
|
|
5102
|
-
aria:
|
|
5103
|
-
text:
|
|
5104
|
-
dom:
|
|
5537
|
+
result: z24.object({
|
|
5538
|
+
url: z24.string(),
|
|
5539
|
+
title: z24.string(),
|
|
5540
|
+
aria: z24.string().optional(),
|
|
5541
|
+
text: z24.string().optional(),
|
|
5542
|
+
dom: z24.record(z24.unknown()).optional()
|
|
5105
5543
|
}),
|
|
5106
5544
|
handler: async (p, ctx) => {
|
|
5107
5545
|
const page = ctx.page;
|
|
@@ -5115,7 +5553,7 @@ var snapshotCommand = registerCommand({
|
|
|
5115
5553
|
if (p.compact || p.c || p.interactive || p.interactiveOnly || p.i) {
|
|
5116
5554
|
observation.compact = formatObservationCompact(observation, { selectors: p.selectors });
|
|
5117
5555
|
}
|
|
5118
|
-
return
|
|
5556
|
+
return ok23(observation, normalizeTips3([
|
|
5119
5557
|
`refs refreshed for ${observation.targets.length} targets; use click @e1 or fill @e2 "text"`
|
|
5120
5558
|
]));
|
|
5121
5559
|
}
|
|
@@ -5123,15 +5561,15 @@ var snapshotCommand = registerCommand({
|
|
|
5123
5561
|
const aria = await captureAriaSnapshot(page, p.selector, p.depth);
|
|
5124
5562
|
const tips = await buildRefTips(page, aria);
|
|
5125
5563
|
persistSemantics(url, aria);
|
|
5126
|
-
return
|
|
5564
|
+
return ok23({ url, title, aria }, normalizeTips3(tips));
|
|
5127
5565
|
}
|
|
5128
5566
|
if (p.type === "text") {
|
|
5129
5567
|
const text = await captureTextSnapshot(page, p.selector);
|
|
5130
|
-
return
|
|
5568
|
+
return ok23({ url, title, text });
|
|
5131
5569
|
}
|
|
5132
5570
|
if (p.type === "dom") {
|
|
5133
5571
|
const dom = await captureDomSnapshot(page, p.selector, p.depth ?? 6);
|
|
5134
|
-
return
|
|
5572
|
+
return ok23({ url, title, dom });
|
|
5135
5573
|
}
|
|
5136
5574
|
if (p.type === "all") {
|
|
5137
5575
|
const [aria, text, dom] = await Promise.all([
|
|
@@ -5141,9 +5579,9 @@ var snapshotCommand = registerCommand({
|
|
|
5141
5579
|
]);
|
|
5142
5580
|
const tips = await buildRefTips(page, aria);
|
|
5143
5581
|
persistSemantics(url, aria);
|
|
5144
|
-
return
|
|
5582
|
+
return ok23({ url, title, aria, text, dom }, normalizeTips3(tips));
|
|
5145
5583
|
}
|
|
5146
|
-
return
|
|
5584
|
+
return fail10(`Unknown snapshot type: ${p.type}`);
|
|
5147
5585
|
}
|
|
5148
5586
|
});
|
|
5149
5587
|
function persistSemantics(url, aria) {
|
|
@@ -5220,22 +5658,22 @@ async function captureDomSnapshot(page, selector, maxDepth) {
|
|
|
5220
5658
|
}
|
|
5221
5659
|
|
|
5222
5660
|
// src/commands/agent.ts
|
|
5223
|
-
import { z as
|
|
5224
|
-
import { ok as
|
|
5661
|
+
import { z as z25 } from "zod";
|
|
5662
|
+
import { ok as ok24, normalizeTips as normalizeTips4 } from "@dyyz1993/xcli-core";
|
|
5225
5663
|
var observeCommand = registerCommand({
|
|
5226
5664
|
name: "observe",
|
|
5227
5665
|
description: "Observe the current page as structured agent targets with session refs",
|
|
5228
5666
|
scope: "page",
|
|
5229
|
-
parameters:
|
|
5230
|
-
includeHidden:
|
|
5231
|
-
limit:
|
|
5232
|
-
compact:
|
|
5233
|
-
selectors:
|
|
5667
|
+
parameters: z25.object({
|
|
5668
|
+
includeHidden: z25.boolean().optional().default(false).describe("Include hidden elements in the target list"),
|
|
5669
|
+
limit: z25.number().int().positive().max(300).optional().default(80).describe("Maximum number of targets to return"),
|
|
5670
|
+
compact: z25.boolean().optional().default(false).describe("Include compact xbrowser style snapshot text"),
|
|
5671
|
+
selectors: z25.boolean().optional().default(false).describe("Include ref to stable CSS selector map")
|
|
5234
5672
|
}),
|
|
5235
|
-
result:
|
|
5236
|
-
targets:
|
|
5237
|
-
selectors:
|
|
5238
|
-
compact:
|
|
5673
|
+
result: z25.object({
|
|
5674
|
+
targets: z25.array(z25.record(z25.unknown())),
|
|
5675
|
+
selectors: z25.record(z25.unknown()).optional(),
|
|
5676
|
+
compact: z25.string().optional()
|
|
5239
5677
|
}).passthrough(),
|
|
5240
5678
|
handler: async (p, ctx) => {
|
|
5241
5679
|
const observation = await observePage(ctx.page, ctx.sessionId, {
|
|
@@ -5244,7 +5682,7 @@ var observeCommand = registerCommand({
|
|
|
5244
5682
|
});
|
|
5245
5683
|
if (p.selectors) observation.selectors = buildSelectorMap(observation);
|
|
5246
5684
|
if (p.compact) observation.compact = formatObservationCompact(observation, { selectors: p.selectors });
|
|
5247
|
-
return
|
|
5685
|
+
return ok24(observation, normalizeTips4([
|
|
5248
5686
|
`refs refreshed for ${observation.targets.length} targets; use act --ref @e1 --action click or click @e1`
|
|
5249
5687
|
]));
|
|
5250
5688
|
}
|
|
@@ -5254,14 +5692,14 @@ var actCommand = registerCommand({
|
|
|
5254
5692
|
description: "Perform an agent action using an observe ref or explicit selector",
|
|
5255
5693
|
scope: "element",
|
|
5256
5694
|
selectorParams: ["selector"],
|
|
5257
|
-
parameters:
|
|
5258
|
-
action:
|
|
5259
|
-
ref:
|
|
5260
|
-
selector:
|
|
5261
|
-
value:
|
|
5262
|
-
key:
|
|
5263
|
-
force:
|
|
5264
|
-
timeout:
|
|
5695
|
+
parameters: z25.object({
|
|
5696
|
+
action: z25.enum(["click", "fill", "type", "press", "select", "check", "hover"]).default("click"),
|
|
5697
|
+
ref: z25.string().optional().describe("Session-scoped ref returned by observe, such as e1"),
|
|
5698
|
+
selector: z25.string().optional().describe("CSS selector fallback when no ref is available"),
|
|
5699
|
+
value: z25.string().optional().describe("Value for fill/type/select"),
|
|
5700
|
+
key: z25.string().optional().describe("Key for press"),
|
|
5701
|
+
force: z25.boolean().optional().default(false).describe("Bypass actionability checks"),
|
|
5702
|
+
timeout: z25.number().optional().default(1e4).describe("Playwright action timeout in milliseconds")
|
|
5265
5703
|
}).refine((p) => !!p.ref || !!p.selector, {
|
|
5266
5704
|
message: "Either ref or selector is required"
|
|
5267
5705
|
}),
|
|
@@ -5275,7 +5713,7 @@ var actCommand = registerCommand({
|
|
|
5275
5713
|
tips: normalizeTips4(result.stale ? ["run observe again to refresh refs"] : [])
|
|
5276
5714
|
};
|
|
5277
5715
|
}
|
|
5278
|
-
return
|
|
5716
|
+
return ok24(result, normalizeTips4(result.stale ? ["ref screen hash changed; run observe if the next action is uncertain"] : []));
|
|
5279
5717
|
}
|
|
5280
5718
|
});
|
|
5281
5719
|
var waitForCommand = registerCommand({
|
|
@@ -5283,16 +5721,16 @@ var waitForCommand = registerCommand({
|
|
|
5283
5721
|
description: "Wait for agent predicates such as text, URL, load state, selector state, or screen hash changes",
|
|
5284
5722
|
scope: "page",
|
|
5285
5723
|
selectorParams: ["selector"],
|
|
5286
|
-
parameters:
|
|
5287
|
-
selector:
|
|
5288
|
-
state:
|
|
5289
|
-
text:
|
|
5290
|
-
url:
|
|
5291
|
-
load:
|
|
5292
|
-
fn:
|
|
5293
|
-
screenHashChanged:
|
|
5294
|
-
timeout:
|
|
5295
|
-
pollInterval:
|
|
5724
|
+
parameters: z25.object({
|
|
5725
|
+
selector: z25.string().optional().describe("CSS selector or observe ref to wait for"),
|
|
5726
|
+
state: z25.enum(["attached", "detached", "visible", "hidden"]).optional().default("visible"),
|
|
5727
|
+
text: z25.string().optional().describe("Visible text to wait for"),
|
|
5728
|
+
url: z25.string().optional().describe("URL substring or glob pattern to wait for"),
|
|
5729
|
+
load: z25.enum(["load", "domcontentloaded", "networkidle"]).optional().describe("Load state to wait for"),
|
|
5730
|
+
fn: z25.string().optional().describe("JavaScript predicate to wait for"),
|
|
5731
|
+
screenHashChanged: z25.string().optional().describe("Previous screenHash from observe"),
|
|
5732
|
+
timeout: z25.number().optional().default(3e4),
|
|
5733
|
+
pollInterval: z25.number().optional().default(200)
|
|
5296
5734
|
}).refine((p) => [p.selector, p.text, p.url, p.load, p.fn, p.screenHashChanged].filter(Boolean).length === 1, {
|
|
5297
5735
|
message: "Provide exactly one wait predicate: selector, text, url, load, fn, or screenHashChanged"
|
|
5298
5736
|
}),
|
|
@@ -5306,30 +5744,30 @@ var waitForCommand = registerCommand({
|
|
|
5306
5744
|
tips: []
|
|
5307
5745
|
};
|
|
5308
5746
|
}
|
|
5309
|
-
return
|
|
5747
|
+
return ok24(result);
|
|
5310
5748
|
}
|
|
5311
5749
|
});
|
|
5312
5750
|
|
|
5313
5751
|
// src/commands/tab.ts
|
|
5314
|
-
import { z as
|
|
5315
|
-
import { ok as
|
|
5316
|
-
var TabParams =
|
|
5317
|
-
subcommand:
|
|
5318
|
-
url:
|
|
5319
|
-
index:
|
|
5752
|
+
import { z as z26 } from "zod";
|
|
5753
|
+
import { ok as ok25, fail as fail11 } from "@dyyz1993/xcli-core";
|
|
5754
|
+
var TabParams = z26.object({
|
|
5755
|
+
subcommand: z26.enum(["list", "new", "close", "switch"]),
|
|
5756
|
+
url: z26.string().optional(),
|
|
5757
|
+
index: z26.number().int().min(0).optional()
|
|
5320
5758
|
});
|
|
5321
5759
|
var tabCommand = registerCommand({
|
|
5322
5760
|
name: "tab",
|
|
5323
5761
|
description: "Manage browser tabs: list, new, close, switch",
|
|
5324
5762
|
scope: "page",
|
|
5325
5763
|
parameters: TabParams,
|
|
5326
|
-
result:
|
|
5327
|
-
success:
|
|
5328
|
-
data:
|
|
5764
|
+
result: z26.object({
|
|
5765
|
+
success: z26.boolean(),
|
|
5766
|
+
data: z26.unknown()
|
|
5329
5767
|
}),
|
|
5330
5768
|
handler: async (p, ctx) => {
|
|
5331
5769
|
if (!ctx.browserContext) {
|
|
5332
|
-
return
|
|
5770
|
+
return fail11("No browser context available. Use --cdp to connect to a browser first.");
|
|
5333
5771
|
}
|
|
5334
5772
|
const pages = ctx.browserContext.pages();
|
|
5335
5773
|
switch (p.subcommand) {
|
|
@@ -5342,7 +5780,7 @@ var tabCommand = registerCommand({
|
|
|
5342
5780
|
case "switch":
|
|
5343
5781
|
return handleSwitch(p, pages, ctx);
|
|
5344
5782
|
default:
|
|
5345
|
-
return
|
|
5783
|
+
return fail11(`Unknown subcommand: ${p.subcommand}`);
|
|
5346
5784
|
}
|
|
5347
5785
|
}
|
|
5348
5786
|
});
|
|
@@ -5357,7 +5795,7 @@ async function handleList(pages, ctx) {
|
|
|
5357
5795
|
if (isActive) activeIndex = i;
|
|
5358
5796
|
tabs.push({ index: i, url, title, active: isActive });
|
|
5359
5797
|
}
|
|
5360
|
-
return
|
|
5798
|
+
return ok25({ tabs, total: tabs.length, activeIndex });
|
|
5361
5799
|
}
|
|
5362
5800
|
async function handleNew(p, _pages, ctx) {
|
|
5363
5801
|
const newPage = await ctx.browserContext.newPage();
|
|
@@ -5385,7 +5823,7 @@ async function handleNew(p, _pages, ctx) {
|
|
|
5385
5823
|
const title = await newPage.title().catch(() => "");
|
|
5386
5824
|
const allPages = ctx.browserContext.pages();
|
|
5387
5825
|
const newIndex = allPages.indexOf(newPage);
|
|
5388
|
-
return
|
|
5826
|
+
return ok25({
|
|
5389
5827
|
index: newIndex >= 0 ? newIndex : allPages.length - 1,
|
|
5390
5828
|
url: newPage.url(),
|
|
5391
5829
|
title,
|
|
@@ -5396,11 +5834,11 @@ async function handleNew(p, _pages, ctx) {
|
|
|
5396
5834
|
async function handleClose(p, ctx) {
|
|
5397
5835
|
const currentPages = ctx.browserContext.pages();
|
|
5398
5836
|
if (currentPages.length <= 1) {
|
|
5399
|
-
return
|
|
5837
|
+
return fail11("Cannot close the last remaining tab");
|
|
5400
5838
|
}
|
|
5401
5839
|
const closeIndex = p.index ?? currentPages.findIndex((pg) => pg === ctx.page);
|
|
5402
5840
|
if (closeIndex < 0 || closeIndex >= currentPages.length) {
|
|
5403
|
-
return
|
|
5841
|
+
return fail11(`Invalid tab index: ${closeIndex}. Valid range: 0-${currentPages.length - 1}`);
|
|
5404
5842
|
}
|
|
5405
5843
|
const pageToClose = currentPages[closeIndex];
|
|
5406
5844
|
const isActivePage = pageToClose === ctx.page;
|
|
@@ -5415,7 +5853,7 @@ async function handleClose(p, ctx) {
|
|
|
5415
5853
|
}
|
|
5416
5854
|
ctx.page = newActivePage;
|
|
5417
5855
|
}
|
|
5418
|
-
return
|
|
5856
|
+
return ok25({
|
|
5419
5857
|
closedIndex: closeIndex,
|
|
5420
5858
|
total: remainingPages.length,
|
|
5421
5859
|
activeIndex: isActivePage ? closeIndex < remainingPages.length ? closeIndex : remainingPages.length - 1 : remainingPages.findIndex((pg) => pg === ctx.page)
|
|
@@ -5423,10 +5861,10 @@ async function handleClose(p, ctx) {
|
|
|
5423
5861
|
}
|
|
5424
5862
|
async function handleSwitch(p, pages, ctx) {
|
|
5425
5863
|
if (p.index === void 0) {
|
|
5426
|
-
return
|
|
5864
|
+
return fail11("Parameter --index is required for switch subcommand");
|
|
5427
5865
|
}
|
|
5428
5866
|
if (p.index < 0 || p.index >= pages.length) {
|
|
5429
|
-
return
|
|
5867
|
+
return fail11(`Invalid tab index: ${p.index}. Valid range: 0-${pages.length - 1}`);
|
|
5430
5868
|
}
|
|
5431
5869
|
const targetPage = pages[p.index];
|
|
5432
5870
|
await targetPage.bringToFront().catch(() => {
|
|
@@ -5436,13 +5874,19 @@ async function handleSwitch(p, pages, ctx) {
|
|
|
5436
5874
|
if (okProbe) break;
|
|
5437
5875
|
await new Promise((r) => setTimeout(r, 300));
|
|
5438
5876
|
}
|
|
5877
|
+
try {
|
|
5878
|
+
const mouse = targetPage.mouse;
|
|
5879
|
+
if (mouse) await mouse.move(1, 1).catch(() => {
|
|
5880
|
+
});
|
|
5881
|
+
} catch {
|
|
5882
|
+
}
|
|
5439
5883
|
const session = ctx.sessionId ? getSessionById(ctx.sessionId) : void 0;
|
|
5440
5884
|
if (session) {
|
|
5441
5885
|
setActivePage(session, targetPage);
|
|
5442
5886
|
}
|
|
5443
5887
|
ctx.page = targetPage;
|
|
5444
5888
|
const title = await targetPage.title().catch(() => "");
|
|
5445
|
-
return
|
|
5889
|
+
return ok25({
|
|
5446
5890
|
index: p.index,
|
|
5447
5891
|
url: targetPage.url(),
|
|
5448
5892
|
title,
|
|
@@ -5451,9 +5895,9 @@ async function handleSwitch(p, pages, ctx) {
|
|
|
5451
5895
|
}
|
|
5452
5896
|
|
|
5453
5897
|
// src/commands/addinitscript.ts
|
|
5454
|
-
import { z as
|
|
5455
|
-
import { ok as
|
|
5456
|
-
import { readFileSync as
|
|
5898
|
+
import { z as z27 } from "zod";
|
|
5899
|
+
import { ok as ok26 } from "@dyyz1993/xcli-core";
|
|
5900
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
5457
5901
|
|
|
5458
5902
|
// src/chain-parser.ts
|
|
5459
5903
|
import { splitCommand, parseCommandArgs } from "@dyyz1993/xcli-core";
|
|
@@ -5593,14 +6037,14 @@ registerCommandDefinition("tab", ["subcommand"]);
|
|
|
5593
6037
|
registerCommandDefinition("mouse", ["action", "x", "y"]);
|
|
5594
6038
|
|
|
5595
6039
|
// src/commands/addinitscript.ts
|
|
5596
|
-
var InitScriptParams =
|
|
5597
|
-
script:
|
|
5598
|
-
file:
|
|
5599
|
-
stdin:
|
|
5600
|
-
name:
|
|
5601
|
-
list:
|
|
5602
|
-
remove:
|
|
5603
|
-
base64:
|
|
6040
|
+
var InitScriptParams = z27.object({
|
|
6041
|
+
script: z27.string().optional(),
|
|
6042
|
+
file: z27.string().optional(),
|
|
6043
|
+
stdin: z27.boolean().optional(),
|
|
6044
|
+
name: z27.string().optional(),
|
|
6045
|
+
list: z27.boolean().optional(),
|
|
6046
|
+
remove: z27.union([z27.string(), z27.boolean()]).optional(),
|
|
6047
|
+
base64: z27.string().optional()
|
|
5604
6048
|
});
|
|
5605
6049
|
var registeredScripts = /* @__PURE__ */ new Map();
|
|
5606
6050
|
function resolveScriptContent(params) {
|
|
@@ -5608,7 +6052,7 @@ function resolveScriptContent(params) {
|
|
|
5608
6052
|
return Buffer.from(params.base64, "base64").toString("utf-8");
|
|
5609
6053
|
}
|
|
5610
6054
|
if (params.file) {
|
|
5611
|
-
return
|
|
6055
|
+
return readFileSync4(params.file, "utf-8");
|
|
5612
6056
|
}
|
|
5613
6057
|
if (params.stdin) {
|
|
5614
6058
|
return null;
|
|
@@ -5634,14 +6078,14 @@ var addInitScriptCommand = registerCommand({
|
|
|
5634
6078
|
description: "Add an initialization script that runs on every page load",
|
|
5635
6079
|
scope: "page",
|
|
5636
6080
|
parameters: InitScriptParams,
|
|
5637
|
-
result:
|
|
5638
|
-
scripts:
|
|
5639
|
-
removed:
|
|
5640
|
-
existed:
|
|
5641
|
-
error:
|
|
5642
|
-
registered:
|
|
5643
|
-
hint:
|
|
5644
|
-
executedImmediately:
|
|
6081
|
+
result: z27.object({
|
|
6082
|
+
scripts: z27.array(z27.object({ name: z27.string(), size: z27.number(), preview: z27.string() })).optional(),
|
|
6083
|
+
removed: z27.string().optional(),
|
|
6084
|
+
existed: z27.boolean().optional(),
|
|
6085
|
+
error: z27.string().optional(),
|
|
6086
|
+
registered: z27.string().optional(),
|
|
6087
|
+
hint: z27.string().optional(),
|
|
6088
|
+
executedImmediately: z27.boolean().optional()
|
|
5645
6089
|
}).passthrough(),
|
|
5646
6090
|
handler: async (params, ctx) => {
|
|
5647
6091
|
if (params.list) {
|
|
@@ -5650,20 +6094,20 @@ var addInitScriptCommand = registerCommand({
|
|
|
5650
6094
|
size: content2.length,
|
|
5651
6095
|
preview: content2.slice(0, 80)
|
|
5652
6096
|
}));
|
|
5653
|
-
return
|
|
6097
|
+
return ok26({ scripts });
|
|
5654
6098
|
}
|
|
5655
6099
|
if (params.remove && typeof params.remove === "string") {
|
|
5656
6100
|
const existed = registeredScripts.delete(params.remove);
|
|
5657
|
-
return
|
|
6101
|
+
return ok26({ removed: params.remove, existed });
|
|
5658
6102
|
}
|
|
5659
6103
|
const removeTarget = (typeof params.remove === "string" ? params.remove : null) || (params.name && !resolveScriptContent(params) ? params.name : null);
|
|
5660
6104
|
if (removeTarget && !resolveScriptContent(params)) {
|
|
5661
6105
|
const existed = registeredScripts.delete(removeTarget);
|
|
5662
|
-
return
|
|
6106
|
+
return ok26({ removed: removeTarget, existed });
|
|
5663
6107
|
}
|
|
5664
6108
|
let content = params.stdin ? await readStdin() : resolveScriptContent(params);
|
|
5665
6109
|
if (!content) {
|
|
5666
|
-
return
|
|
6110
|
+
return ok26({ error: "No script content provided. Use --script, --file, --stdin, or --base64" });
|
|
5667
6111
|
}
|
|
5668
6112
|
const scriptName = params.name ?? `script-${Date.now()}`;
|
|
5669
6113
|
registeredScripts.set(scriptName, content);
|
|
@@ -5671,45 +6115,45 @@ var addInitScriptCommand = registerCommand({
|
|
|
5671
6115
|
try {
|
|
5672
6116
|
await ctx.page.evaluate(content);
|
|
5673
6117
|
} catch {
|
|
5674
|
-
return
|
|
6118
|
+
return ok26({
|
|
5675
6119
|
registered: scriptName,
|
|
5676
6120
|
hint: "Script registered for future page loads; immediate execution skipped (page may not be ready)"
|
|
5677
6121
|
});
|
|
5678
6122
|
}
|
|
5679
|
-
return
|
|
6123
|
+
return ok26({ registered: scriptName, executedImmediately: true });
|
|
5680
6124
|
}
|
|
5681
6125
|
});
|
|
5682
6126
|
registerCommandDefinition("addinitscript", ["script"]);
|
|
5683
6127
|
|
|
5684
6128
|
// src/commands/find.ts
|
|
5685
|
-
import { z as
|
|
5686
|
-
import { ok as
|
|
5687
|
-
var actionSchema2 =
|
|
6129
|
+
import { z as z28 } from "zod";
|
|
6130
|
+
import { ok as ok27, fail as fail12, normalizeTips as normalizeTips5 } from "@dyyz1993/xcli-core";
|
|
6131
|
+
var actionSchema2 = z28.enum(["click", "fill", "type", "select", "hover", "check"]);
|
|
5688
6132
|
var findCommand = registerCommand({
|
|
5689
6133
|
name: "find",
|
|
5690
6134
|
description: "Find elements by semantic strategy (text/role/label/placeholder/testid) and optionally perform an action",
|
|
5691
6135
|
scope: "page",
|
|
5692
|
-
parameters:
|
|
5693
|
-
strategy:
|
|
5694
|
-
value:
|
|
5695
|
-
name:
|
|
5696
|
-
exact:
|
|
5697
|
-
operation:
|
|
6136
|
+
parameters: z28.object({
|
|
6137
|
+
strategy: z28.enum(["text", "role", "label", "placeholder", "testid", "alt", "title", "first", "last", "nth"]),
|
|
6138
|
+
value: z28.string(),
|
|
6139
|
+
name: z28.string().optional(),
|
|
6140
|
+
exact: z28.boolean().optional().default(false),
|
|
6141
|
+
operation: z28.string().optional().describe('Trailing operation syntax, e.g. click, fill "text", type "text"'),
|
|
5698
6142
|
action: actionSchema2.optional().describe("Action to perform when not using trailing operation syntax"),
|
|
5699
|
-
actionValue:
|
|
5700
|
-
index:
|
|
5701
|
-
click:
|
|
5702
|
-
fill:
|
|
5703
|
-
type:
|
|
5704
|
-
select:
|
|
5705
|
-
hover:
|
|
5706
|
-
check:
|
|
5707
|
-
timeout:
|
|
6143
|
+
actionValue: z28.string().optional().describe("Value for fill/type/select when using action"),
|
|
6144
|
+
index: z28.number().int().optional().describe("Index for nth strategy"),
|
|
6145
|
+
click: z28.boolean().optional().default(false),
|
|
6146
|
+
fill: z28.string().optional(),
|
|
6147
|
+
type: z28.string().optional(),
|
|
6148
|
+
select: z28.string().optional(),
|
|
6149
|
+
hover: z28.boolean().optional().default(false),
|
|
6150
|
+
check: z28.boolean().optional().default(false),
|
|
6151
|
+
timeout: z28.number().optional().default(1e4)
|
|
5708
6152
|
}),
|
|
5709
|
-
result:
|
|
5710
|
-
matched:
|
|
5711
|
-
selector:
|
|
5712
|
-
action:
|
|
6153
|
+
result: z28.object({
|
|
6154
|
+
matched: z28.number(),
|
|
6155
|
+
selector: z28.string(),
|
|
6156
|
+
action: z28.string().optional()
|
|
5713
6157
|
}),
|
|
5714
6158
|
handler: async (p, ctx) => {
|
|
5715
6159
|
const page = ctx.page;
|
|
@@ -5724,7 +6168,7 @@ var findCommand = registerCommand({
|
|
|
5724
6168
|
});
|
|
5725
6169
|
const count = await locator.count();
|
|
5726
6170
|
if (count === 0) {
|
|
5727
|
-
return
|
|
6171
|
+
return fail12(`No element found with ${p.strategy}="${p.value}"`);
|
|
5728
6172
|
}
|
|
5729
6173
|
const tips = [];
|
|
5730
6174
|
const target = selectTarget(locator, p.strategy);
|
|
@@ -5736,15 +6180,15 @@ var findCommand = registerCommand({
|
|
|
5736
6180
|
await target.click({ timeout: p.timeout, force: true });
|
|
5737
6181
|
return okWithTips({ matched: count, selector, action: "click" }, tips);
|
|
5738
6182
|
} else if (actionName === "fill") {
|
|
5739
|
-
if (actionValue === void 0) return
|
|
6183
|
+
if (actionValue === void 0) return fail12("find fill requires a value");
|
|
5740
6184
|
await target.fill(actionValue, { timeout: p.timeout, force: true });
|
|
5741
6185
|
return okWithTips({ matched: count, selector, action: `fill("${actionValue}")` }, tips);
|
|
5742
6186
|
} else if (actionName === "type") {
|
|
5743
|
-
if (actionValue === void 0) return
|
|
6187
|
+
if (actionValue === void 0) return fail12("find type requires a value");
|
|
5744
6188
|
await target.type(actionValue, { delay: 10, timeout: p.timeout });
|
|
5745
6189
|
return okWithTips({ matched: count, selector, action: `type("${actionValue}")` }, tips);
|
|
5746
6190
|
} else if (actionName === "select") {
|
|
5747
|
-
if (actionValue === void 0) return
|
|
6191
|
+
if (actionValue === void 0) return fail12("find select requires a value");
|
|
5748
6192
|
await target.selectOption(actionValue);
|
|
5749
6193
|
return okWithTips({ matched: count, selector, action: `select("${actionValue}")` }, tips);
|
|
5750
6194
|
} else if (actionName === "hover") {
|
|
@@ -5758,7 +6202,7 @@ var findCommand = registerCommand({
|
|
|
5758
6202
|
}
|
|
5759
6203
|
});
|
|
5760
6204
|
function okWithTips(data, tips) {
|
|
5761
|
-
const result =
|
|
6205
|
+
const result = ok27(data);
|
|
5762
6206
|
if (tips.length > 0) result.tips = normalizeTips5(tips);
|
|
5763
6207
|
return result;
|
|
5764
6208
|
}
|
|
@@ -6469,7 +6913,7 @@ async function loadHooks() {
|
|
|
6469
6913
|
|
|
6470
6914
|
// src/executor.ts
|
|
6471
6915
|
import { homedir as homedir5 } from "os";
|
|
6472
|
-
import { join as
|
|
6916
|
+
import { join as join7 } from "path";
|
|
6473
6917
|
function levenshtein(a, b) {
|
|
6474
6918
|
const m = a.length, n = b.length;
|
|
6475
6919
|
const dp = Array(m + 1).fill(null).map(() => Array(n + 1).fill(0));
|
|
@@ -6484,7 +6928,7 @@ function levenshtein(a, b) {
|
|
|
6484
6928
|
}
|
|
6485
6929
|
var NAVIGATION_COMMANDS = /* @__PURE__ */ new Set(["goto", "back", "forward", "refresh"]);
|
|
6486
6930
|
var snapshotHintShown = /* @__PURE__ */ new WeakSet();
|
|
6487
|
-
var CONFIG_DIR2 =
|
|
6931
|
+
var CONFIG_DIR2 = join7(homedir5(), ".xbrowser");
|
|
6488
6932
|
var storageCache = /* @__PURE__ */ new Map();
|
|
6489
6933
|
function getPluginStorage(pluginName) {
|
|
6490
6934
|
if (!storageCache.has(pluginName)) {
|
|
@@ -6496,7 +6940,7 @@ var archiveInitialized = false;
|
|
|
6496
6940
|
function ensureArchiveInit() {
|
|
6497
6941
|
if (!archiveInitialized) {
|
|
6498
6942
|
try {
|
|
6499
|
-
configureArchiveStore({ archiveDir:
|
|
6943
|
+
configureArchiveStore({ archiveDir: join7(homedir5(), ".xbrowser", "archives") });
|
|
6500
6944
|
} catch {
|
|
6501
6945
|
}
|
|
6502
6946
|
archiveInitialized = true;
|
|
@@ -6520,7 +6964,7 @@ async function guardCheck(commandName) {
|
|
|
6520
6964
|
}
|
|
6521
6965
|
}
|
|
6522
6966
|
function errorResult(message) {
|
|
6523
|
-
return { ...
|
|
6967
|
+
return { ...fail13(message), duration: 0 };
|
|
6524
6968
|
}
|
|
6525
6969
|
function tipsToMessages(tips) {
|
|
6526
6970
|
if (!tips || tips.length === 0) return [];
|
|
@@ -6597,7 +7041,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
6597
7041
|
}
|
|
6598
7042
|
let targetPageOverride = null;
|
|
6599
7043
|
if (_target && extraOpts?.cdpEndpoint) {
|
|
6600
|
-
const { findTargetPage } = await import("./browser-
|
|
7044
|
+
const { findTargetPage } = await import("./browser-AQ2AT2YZ.js");
|
|
6601
7045
|
targetPageOverride = await findTargetPage(extraOpts.cdpEndpoint, _target);
|
|
6602
7046
|
if (!targetPageOverride) {
|
|
6603
7047
|
return errorResult(`Target "${_target}" not found. Use 'xbrowser targets --cdp ${extraOpts.cdpEndpoint}' to list available pages.`);
|
|
@@ -6614,8 +7058,15 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
6614
7058
|
params = result.data;
|
|
6615
7059
|
}
|
|
6616
7060
|
if (command.scope !== "cli" && !process.env.XBROWSER_DAEMON_WORKER) {
|
|
6617
|
-
const { forwardExec } = await import("./daemon-client-
|
|
6618
|
-
const
|
|
7061
|
+
const { forwardExec } = await import("./daemon-client-7D6EZNOE.js");
|
|
7062
|
+
const longRunning = /^(vision-task|crawl|scrape|record|replay)$/.test(commandName);
|
|
7063
|
+
const result = await forwardExec(
|
|
7064
|
+
commandName,
|
|
7065
|
+
params,
|
|
7066
|
+
sessionName,
|
|
7067
|
+
extraOpts?.cdpEndpoint,
|
|
7068
|
+
longRunning ? 20 * 6e4 : void 0
|
|
7069
|
+
);
|
|
6619
7070
|
if (result) return result;
|
|
6620
7071
|
}
|
|
6621
7072
|
let session;
|
|
@@ -6783,7 +7234,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
6783
7234
|
timestamp: start
|
|
6784
7235
|
});
|
|
6785
7236
|
if (isSuccess) {
|
|
6786
|
-
return { ...
|
|
7237
|
+
return { ...ok28(raw.data, merged.length > 0 ? merged : raw.tips), duration, ...hookOutputs ? { hookOutputs } : {} };
|
|
6787
7238
|
}
|
|
6788
7239
|
return { success: false, data: raw.data, message: raw.message, tips: mergedOrRaw, duration, ...hookOutputs ? { hookOutputs } : {} };
|
|
6789
7240
|
}
|
|
@@ -6797,14 +7248,14 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
6797
7248
|
duration,
|
|
6798
7249
|
timestamp: start
|
|
6799
7250
|
});
|
|
6800
|
-
return { ...
|
|
7251
|
+
return { ...ok28(raw, smartTipNormalized), duration, ...hookOutputs ? { hookOutputs } : {} };
|
|
6801
7252
|
} catch (err) {
|
|
6802
7253
|
const end = Date.now();
|
|
6803
7254
|
const duration = end - start;
|
|
6804
7255
|
const errorMessage = errMsg(err);
|
|
6805
7256
|
if (session?.page && process.env.XBROWSER_RECOVERY && !extraOpts?._recoveryAttempted) {
|
|
6806
7257
|
try {
|
|
6807
|
-
const { attemptRecovery } = await import("./recovery-
|
|
7258
|
+
const { attemptRecovery } = await import("./recovery-Z3RDZENA.js");
|
|
6808
7259
|
const recovery = await attemptRecovery(
|
|
6809
7260
|
session.page,
|
|
6810
7261
|
sessionName,
|
|
@@ -6842,7 +7293,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
6842
7293
|
duration,
|
|
6843
7294
|
timestamp: start
|
|
6844
7295
|
});
|
|
6845
|
-
return { ...
|
|
7296
|
+
return { ...fail13(errorMessage), duration };
|
|
6846
7297
|
} finally {
|
|
6847
7298
|
}
|
|
6848
7299
|
}
|
|
@@ -6883,7 +7334,7 @@ async function executeChain(input, options) {
|
|
|
6883
7334
|
results.push({
|
|
6884
7335
|
command: cmdName,
|
|
6885
7336
|
raw: cmdStr,
|
|
6886
|
-
...
|
|
7337
|
+
...fail13(`Plugin "${cmdName}" requires a sub-command`),
|
|
6887
7338
|
duration: 0
|
|
6888
7339
|
});
|
|
6889
7340
|
if (type === "and") {
|
|
@@ -6902,7 +7353,7 @@ async function executeChain(input, options) {
|
|
|
6902
7353
|
results.push({
|
|
6903
7354
|
command: cmdName,
|
|
6904
7355
|
raw: cmdStr,
|
|
6905
|
-
...
|
|
7356
|
+
...fail13(`Unknown command "${subCommand}" for plugin "${cmdName}"`),
|
|
6906
7357
|
duration: 0
|
|
6907
7358
|
});
|
|
6908
7359
|
if (type === "and") {
|
|
@@ -7006,7 +7457,7 @@ async function executeChain(input, options) {
|
|
|
7006
7457
|
results.push({
|
|
7007
7458
|
command: `${cmdName} ${subCommand}`,
|
|
7008
7459
|
raw: cmdStr,
|
|
7009
|
-
...
|
|
7460
|
+
...ok28(data),
|
|
7010
7461
|
duration: duration2,
|
|
7011
7462
|
...hookOutputs ? { hookOutputs } : {}
|
|
7012
7463
|
});
|
|
@@ -7034,7 +7485,7 @@ async function executeChain(input, options) {
|
|
|
7034
7485
|
results.push({
|
|
7035
7486
|
command: `${cmdName} ${subCommand}`,
|
|
7036
7487
|
raw: cmdStr,
|
|
7037
|
-
...
|
|
7488
|
+
...fail13(errorMessage),
|
|
7038
7489
|
duration: duration2
|
|
7039
7490
|
});
|
|
7040
7491
|
if (type === "and") {
|
|
@@ -7338,10 +7789,10 @@ async function replayEntry(entry, options = {}) {
|
|
|
7338
7789
|
}
|
|
7339
7790
|
|
|
7340
7791
|
// src/daemon/feedback-store.ts
|
|
7341
|
-
import { readFileSync as
|
|
7342
|
-
import { join as
|
|
7792
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4 } from "fs";
|
|
7793
|
+
import { join as join8 } from "path";
|
|
7343
7794
|
import { homedir as homedir6 } from "os";
|
|
7344
|
-
var FEEDBACK_FILE =
|
|
7795
|
+
var FEEDBACK_FILE = join8(homedir6(), ".xbrowser", "feedback.json");
|
|
7345
7796
|
var FeedbackStore = class {
|
|
7346
7797
|
entries = [];
|
|
7347
7798
|
constructor() {
|
|
@@ -7349,7 +7800,7 @@ var FeedbackStore = class {
|
|
|
7349
7800
|
}
|
|
7350
7801
|
load() {
|
|
7351
7802
|
try {
|
|
7352
|
-
const data =
|
|
7803
|
+
const data = readFileSync5(FEEDBACK_FILE, "utf8");
|
|
7353
7804
|
this.entries = JSON.parse(data);
|
|
7354
7805
|
} catch {
|
|
7355
7806
|
this.entries = [];
|
|
@@ -7357,7 +7808,7 @@ var FeedbackStore = class {
|
|
|
7357
7808
|
}
|
|
7358
7809
|
save() {
|
|
7359
7810
|
try {
|
|
7360
|
-
mkdirSync4(
|
|
7811
|
+
mkdirSync4(join8(homedir6(), ".xbrowser"), { recursive: true });
|
|
7361
7812
|
writeFileSync4(FEEDBACK_FILE, JSON.stringify(this.entries, null, 2));
|
|
7362
7813
|
} catch {
|
|
7363
7814
|
}
|
|
@@ -7841,7 +8292,7 @@ function createRPCHandler() {
|
|
|
7841
8292
|
return result;
|
|
7842
8293
|
} catch (err) {
|
|
7843
8294
|
const errorMessage = errMsg(err);
|
|
7844
|
-
const { attemptRecovery } = await import("./recovery-
|
|
8295
|
+
const { attemptRecovery } = await import("./recovery-Z3RDZENA.js");
|
|
7845
8296
|
const recovery = await attemptRecovery(
|
|
7846
8297
|
session?.page,
|
|
7847
8298
|
sessionName,
|
|
@@ -8381,7 +8832,7 @@ function createRPCHandler() {
|
|
|
8381
8832
|
let rawContent;
|
|
8382
8833
|
let parsed;
|
|
8383
8834
|
try {
|
|
8384
|
-
rawContent =
|
|
8835
|
+
rawContent = readFileSync7(file, "utf8");
|
|
8385
8836
|
try {
|
|
8386
8837
|
parsed = JSON.parse(rawContent);
|
|
8387
8838
|
} catch {
|
|
@@ -8406,7 +8857,7 @@ function createRPCHandler() {
|
|
|
8406
8857
|
if (isNewFormat) {
|
|
8407
8858
|
try {
|
|
8408
8859
|
const replayErrors = [];
|
|
8409
|
-
const { SessionReplayer } = await import("./session-replayer-
|
|
8860
|
+
const { SessionReplayer } = await import("./session-replayer-UVH5FGY2.js");
|
|
8410
8861
|
const replayer = new SessionReplayer({
|
|
8411
8862
|
page: session.page,
|
|
8412
8863
|
stepDelay: slowMo * 500,
|
|
@@ -8456,10 +8907,10 @@ function createRPCHandler() {
|
|
|
8456
8907
|
const errors = Array.isArray(input.errors) ? input.errors : [];
|
|
8457
8908
|
const eventsPlayed = typeof input.eventsPlayed === "number" ? input.eventsPlayed : 0;
|
|
8458
8909
|
const totalEvents = typeof input.totalEvents === "number" ? input.totalEvents : eventsPlayed + errors.length;
|
|
8459
|
-
const
|
|
8910
|
+
const ok29 = input.ok ?? errors.length === 0;
|
|
8460
8911
|
return {
|
|
8461
|
-
ok:
|
|
8462
|
-
success:
|
|
8912
|
+
ok: ok29,
|
|
8913
|
+
success: ok29,
|
|
8463
8914
|
duration: typeof input.duration === "number" ? input.duration : 0,
|
|
8464
8915
|
eventsPlayed,
|
|
8465
8916
|
totalEvents,
|
|
@@ -9458,12 +9909,12 @@ var FileListHandler = class {
|
|
|
9458
9909
|
const msg = ctx.message;
|
|
9459
9910
|
try {
|
|
9460
9911
|
const { readdirSync, statSync } = await import("fs");
|
|
9461
|
-
const { join:
|
|
9912
|
+
const { join: join10, resolve } = await import("path");
|
|
9462
9913
|
const targetPath = resolve(msg.path);
|
|
9463
9914
|
const entries = readdirSync(targetPath);
|
|
9464
9915
|
const files = entries.map((name) => {
|
|
9465
9916
|
try {
|
|
9466
|
-
const stat = statSync(
|
|
9917
|
+
const stat = statSync(join10(targetPath, name));
|
|
9467
9918
|
return { name, isDir: stat.isDirectory(), size: stat.size, modified: stat.mtime.toISOString() };
|
|
9468
9919
|
} catch {
|
|
9469
9920
|
return { name, isDir: false, size: 0, modified: "" };
|
|
@@ -9480,10 +9931,10 @@ var FileDownloadHandler = class {
|
|
|
9480
9931
|
async handle(ctx) {
|
|
9481
9932
|
const msg = ctx.message;
|
|
9482
9933
|
try {
|
|
9483
|
-
const { readFileSync:
|
|
9934
|
+
const { readFileSync: readFileSync8 } = await import("fs");
|
|
9484
9935
|
const { resolve, basename } = await import("path");
|
|
9485
9936
|
const targetPath = resolve(msg.path);
|
|
9486
|
-
const data =
|
|
9937
|
+
const data = readFileSync8(targetPath);
|
|
9487
9938
|
const base64 = data.toString("base64");
|
|
9488
9939
|
const ext = targetPath.split(".").pop()?.toLowerCase() || "";
|
|
9489
9940
|
const mimeMap = {
|
|
@@ -11473,8 +11924,8 @@ connectWS();
|
|
|
11473
11924
|
}
|
|
11474
11925
|
|
|
11475
11926
|
// src/daemon/daemon-main.ts
|
|
11476
|
-
var CONFIG_DIR3 =
|
|
11477
|
-
var LOG_FILE =
|
|
11927
|
+
var CONFIG_DIR3 = join9(homedir7(), ".xbrowser");
|
|
11928
|
+
var LOG_FILE = join9(CONFIG_DIR3, "daemon.log");
|
|
11478
11929
|
function log(msg) {
|
|
11479
11930
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").substring(0, 19);
|
|
11480
11931
|
const line = `[DAEMON ${ts}] ${msg}
|
|
@@ -11506,7 +11957,7 @@ async function main() {
|
|
|
11506
11957
|
if (err.code === "EADDRINUSE") {
|
|
11507
11958
|
log(`Port ${daemonPort} already in use \u2014 another daemon instance likely won the startup race. Exiting gracefully.`);
|
|
11508
11959
|
try {
|
|
11509
|
-
unlinkSync(
|
|
11960
|
+
unlinkSync(join9(CONFIG_DIR3, "daemon.json"));
|
|
11510
11961
|
} catch {
|
|
11511
11962
|
}
|
|
11512
11963
|
process.exit(0);
|
|
@@ -11548,7 +11999,7 @@ async function main() {
|
|
|
11548
11999
|
});
|
|
11549
12000
|
});
|
|
11550
12001
|
mkdirSync5(CONFIG_DIR3, { recursive: true });
|
|
11551
|
-
writeFileSync5(
|
|
12002
|
+
writeFileSync5(join9(CONFIG_DIR3, "daemon.json"), JSON.stringify({
|
|
11552
12003
|
port: daemonPort,
|
|
11553
12004
|
pid: process.pid,
|
|
11554
12005
|
startedAt: Date.now()
|