@xbrowser/cli 1.10.0 → 1.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{anti-bot-DR56Y63V.js → anti-bot-GTTYNEFB.js} +1 -1
- package/dist/{browser-WQZ3D6AE.js → browser-3HQKDZQ6.js} +4 -2
- package/dist/{browser-T3V3JWVH.js → browser-ILXTEWH4.js} +1 -1
- package/dist/{browser-HBL72GPZ.js → browser-XPDMY2NX.js} +3 -3
- package/dist/{cdp-driver-J3YQ4LJV.js → cdp-driver-ESH3PDE6.js} +2 -1
- package/dist/cdp-driver-N2E2ZNSC.js +4644 -0
- package/dist/cdp-driver-YIKR4BUX.js +49 -0
- package/dist/chunk-3FWLW7FS.js +106 -0
- package/dist/chunk-A6LPGFAL.js +437 -0
- package/dist/{chunk-RRBXV7KE.js → chunk-DKM2JOZL.js} +633 -63
- package/dist/{chunk-ABXMBNQ6.js → chunk-H2A5JUK5.js} +37 -466
- package/dist/{chunk-5UGR6MUK.js → chunk-HBHOKZPN.js} +51 -16
- package/dist/{chunk-BNO7OKO4.js → chunk-HBMEFSTB.js} +39 -0
- package/dist/{chunk-INTQPBYF.js → chunk-JKVUFP3G.js} +6 -2
- package/dist/chunk-KJTABK3Z.js +1255 -0
- package/dist/{chunk-DWDXEGVK.js → chunk-NKW4A74J.js} +9 -3
- package/dist/{chunk-4452SPFI.js → chunk-NODRQGOK.js} +51 -16
- package/dist/{chunk-SQHSZENE.js → chunk-OMU63E6J.js} +3 -22
- package/dist/{chunk-NITFVWWS.js → chunk-OVW2UEWN.js} +981 -407
- package/dist/{chunk-WSCP7QCJ.js → chunk-R6IJ6PIV.js} +14 -11
- package/dist/{chunk-YMUSHPU4.js → chunk-SH6OTPXN.js} +46 -16
- package/dist/{chunk-MWFHZUIY.js → chunk-TEXCXIBW.js} +1 -1
- package/dist/{cdp-driver-2T4P4ZE2.js → chunk-Z6CUR3VG.js} +1542 -134
- package/dist/{chunk-IIM5GOD7.js → chunk-ZTHE5RBZ.js} +7 -1
- package/dist/cli.js +1136 -584
- package/dist/{daemon-client-GKEPT4NY.js → daemon-client-7D6EZNOE.js} +46 -16
- package/dist/{daemon-client-O6BYVRXV.js → daemon-client-HEGAXWGK.js} +1 -1
- package/dist/daemon-main.js +1031 -522
- package/dist/{human-interaction-5AO42MBA.js → human-interaction-4YR2N6R6.js} +2 -2
- package/dist/{human-interaction-C5OEGXGO.js → human-interaction-Y5IDH6LD.js} +1 -1
- package/dist/{human-interaction-ISXZTAYY.js → human-interaction-ZUTR5AC2.js} +2 -2
- package/dist/index.d.ts +10 -0
- package/dist/index.js +1147 -593
- package/dist/{proxy-C6CK3UH5.js → proxy-LUR4U5YF.js} +2 -1
- package/dist/{recovery-J2ISVGUL.js → recovery-FTZGV6VY.js} +2 -2
- package/dist/{recovery-ZJVVHP7N.js → recovery-L5GLDX4O.js} +1 -1
- package/dist/{recovery-EF33LKRJ.js → recovery-Z3RDZENA.js} +2 -2
- package/dist/{session-recorder-H3KEYU26.js → session-recorder-3BEVWHOK.js} +1 -1
- package/dist/{session-recorder-QRZMKFVL.js → session-recorder-SLDBENVF.js} +1 -1
- package/dist/{session-replayer-LJUC4TI7.js → session-replayer-K4A6OI4M.js} +90 -2
- package/package.json +1 -1
- package/dist/chunk-BAHSRZIX.js +0 -2191
package/dist/daemon-main.js
CHANGED
|
@@ -15,12 +15,11 @@ import {
|
|
|
15
15
|
} from "./chunk-VEDJ5XSQ.js";
|
|
16
16
|
import {
|
|
17
17
|
SessionRecorder
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-HBHOKZPN.js";
|
|
19
19
|
import {
|
|
20
20
|
closeEphemeralContext,
|
|
21
21
|
closeSessionByName,
|
|
22
22
|
createEphemeralContext,
|
|
23
|
-
createRuleEngine,
|
|
24
23
|
createSession,
|
|
25
24
|
findOrRestoreSession,
|
|
26
25
|
findSession,
|
|
@@ -31,8 +30,13 @@ import {
|
|
|
31
30
|
resolveLaunchOpts,
|
|
32
31
|
saveSessionDiskMeta,
|
|
33
32
|
setActivePage
|
|
34
|
-
} from "./chunk-
|
|
35
|
-
import
|
|
33
|
+
} from "./chunk-KJTABK3Z.js";
|
|
34
|
+
import {
|
|
35
|
+
createRuleEngine
|
|
36
|
+
} from "./chunk-Z6CUR3VG.js";
|
|
37
|
+
import {
|
|
38
|
+
queryJS
|
|
39
|
+
} from "./chunk-3FWLW7FS.js";
|
|
36
40
|
import "./chunk-TNEN6VQ2.js";
|
|
37
41
|
import {
|
|
38
42
|
errMsg
|
|
@@ -40,20 +44,21 @@ import {
|
|
|
40
44
|
import {
|
|
41
45
|
detectAntiBot,
|
|
42
46
|
formatDetectionMessage
|
|
43
|
-
} from "./chunk-
|
|
47
|
+
} from "./chunk-JKVUFP3G.js";
|
|
44
48
|
import {
|
|
45
|
-
ScreencastCapturer
|
|
46
|
-
|
|
49
|
+
ScreencastCapturer,
|
|
50
|
+
resolveScreenshotsDir
|
|
51
|
+
} from "./chunk-HBMEFSTB.js";
|
|
47
52
|
import "./chunk-KFQGP6VL.js";
|
|
48
53
|
|
|
49
54
|
// src/daemon/daemon-main.ts
|
|
50
55
|
import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, appendFileSync, unlinkSync } from "fs";
|
|
51
|
-
import { join as
|
|
56
|
+
import { join as join9 } from "path";
|
|
52
57
|
import { homedir as homedir7 } from "os";
|
|
53
58
|
import { startHttpServer } from "@dyyz1993/xcli-core";
|
|
54
59
|
|
|
55
60
|
// src/daemon/rpc-handlers.ts
|
|
56
|
-
import { readFileSync as
|
|
61
|
+
import { readFileSync as readFileSync7 } from "fs";
|
|
57
62
|
import {
|
|
58
63
|
createSessionMeta,
|
|
59
64
|
removeSession
|
|
@@ -61,8 +66,8 @@ import {
|
|
|
61
66
|
|
|
62
67
|
// src/executor.ts
|
|
63
68
|
import {
|
|
64
|
-
ok as
|
|
65
|
-
fail as
|
|
69
|
+
ok as ok28,
|
|
70
|
+
fail as fail13,
|
|
66
71
|
isCommandResult,
|
|
67
72
|
CompositeStorage as CompositeStorage2,
|
|
68
73
|
TipCollector as TipCollector2,
|
|
@@ -416,14 +421,23 @@ var CAPTCHA_SELECTORS = {
|
|
|
416
421
|
generic: '#captcha, .captcha-container, [class*="captcha"]'
|
|
417
422
|
};
|
|
418
423
|
async function detectCaptcha(page) {
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
424
|
+
const hit = await page.evaluate(`
|
|
425
|
+
(function() {
|
|
426
|
+
var S = ${JSON.stringify(CAPTCHA_SELECTORS)};
|
|
427
|
+
for (var type in S) {
|
|
428
|
+
var sel = S[type];
|
|
429
|
+
var el;
|
|
430
|
+
try { el = document.querySelector(sel); } catch (e) { continue; }
|
|
431
|
+
if (el) {
|
|
432
|
+
var url = el.getAttribute && el.getAttribute('src');
|
|
433
|
+
return { type: type, selector: sel, iframeUrl: url || undefined };
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
return null;
|
|
437
|
+
})()
|
|
438
|
+
`).catch(() => null);
|
|
439
|
+
if (!hit) return null;
|
|
440
|
+
return { type: hit.type, selector: hit.selector, iframeUrl: hit.iframeUrl || void 0 };
|
|
427
441
|
}
|
|
428
442
|
async function waitForCaptchaSolved(page, captchaInfo, timeoutMs = 18e4) {
|
|
429
443
|
const startTime = Date.now();
|
|
@@ -474,12 +488,24 @@ var clickCommand = registerCommand({
|
|
|
474
488
|
let detectedNewPage;
|
|
475
489
|
let cleanup;
|
|
476
490
|
if (ctx.browserContext?.on) {
|
|
491
|
+
const targetIdOf = (p2) => p2._targetId;
|
|
492
|
+
let targetIdsBefore;
|
|
493
|
+
try {
|
|
494
|
+
targetIdsBefore = new Set(ctx.browserContext.pages().map(targetIdOf));
|
|
495
|
+
} catch {
|
|
496
|
+
targetIdsBefore = /* @__PURE__ */ new Set();
|
|
497
|
+
}
|
|
477
498
|
const pagePromise = new Promise((resolve) => {
|
|
478
499
|
const timer = setTimeout(() => {
|
|
479
500
|
ctx.browserContext.off("page", handler);
|
|
480
501
|
resolve(void 0);
|
|
481
502
|
}, 3e3);
|
|
482
503
|
const handler = (page2) => {
|
|
504
|
+
if (targetIdsBefore.has(targetIdOf(page2))) return;
|
|
505
|
+
try {
|
|
506
|
+
if ((page2.url?.() ?? "") === "about:blank") return;
|
|
507
|
+
} catch {
|
|
508
|
+
}
|
|
483
509
|
clearTimeout(timer);
|
|
484
510
|
ctx.browserContext.off("page", handler);
|
|
485
511
|
resolve(page2);
|
|
@@ -880,9 +906,11 @@ var mouseCommand = registerCommand({
|
|
|
880
906
|
await ctx.page.mouse.move(p.x, p.y, { steps: p.steps || 1 });
|
|
881
907
|
break;
|
|
882
908
|
case "down":
|
|
909
|
+
await ctx.page.mouse.move(p.x, p.y);
|
|
883
910
|
await ctx.page.mouse.down({ button });
|
|
884
911
|
break;
|
|
885
912
|
case "up":
|
|
913
|
+
await ctx.page.mouse.move(p.x, p.y);
|
|
886
914
|
await ctx.page.mouse.up({ button });
|
|
887
915
|
break;
|
|
888
916
|
case "click":
|
|
@@ -896,28 +924,441 @@ var mouseCommand = registerCommand({
|
|
|
896
924
|
}
|
|
897
925
|
});
|
|
898
926
|
|
|
899
|
-
// src/commands/
|
|
927
|
+
// src/commands/find-visual.ts
|
|
900
928
|
import { z as z7 } from "zod";
|
|
901
|
-
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";
|
|
902
1333
|
var evaluateCommand = registerCommand({
|
|
903
1334
|
name: "eval",
|
|
904
|
-
description: "Evaluate JavaScript expression in the browser",
|
|
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)",
|
|
905
1336
|
scope: "page",
|
|
906
|
-
parameters:
|
|
907
|
-
expression:
|
|
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")
|
|
908
1344
|
}),
|
|
909
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
|
+
}
|
|
910
1351
|
const engine = createRuleEngine();
|
|
911
1352
|
engine.start();
|
|
912
1353
|
const decision = engine.evaluate({
|
|
913
1354
|
method: "Runtime.evaluate",
|
|
914
|
-
params: { expression
|
|
1355
|
+
params: { expression },
|
|
915
1356
|
sessionId: "eval-check",
|
|
916
1357
|
direction: "client\u2192browser"
|
|
917
1358
|
});
|
|
918
1359
|
engine.stop();
|
|
919
|
-
const result = await ctx.page.
|
|
920
|
-
const response =
|
|
1360
|
+
const result = p.frame ? await ctx.page.evaluateInFrame(p.frame, expression) : await ctx.page.evaluate(expression);
|
|
1361
|
+
const response = ok9({ result });
|
|
921
1362
|
if (decision && decision.severity === "danger") {
|
|
922
1363
|
response.tips = normalizeTips2([`${decision.suggestion}`]);
|
|
923
1364
|
}
|
|
@@ -925,37 +1366,65 @@ var evaluateCommand = registerCommand({
|
|
|
925
1366
|
}
|
|
926
1367
|
});
|
|
927
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
|
+
|
|
928
1397
|
// src/commands/storage.ts
|
|
929
|
-
import { z as
|
|
930
|
-
import { ok as
|
|
1398
|
+
import { z as z11 } from "zod";
|
|
1399
|
+
import { ok as ok11, fail as fail4 } from "@dyyz1993/xcli-core";
|
|
931
1400
|
var getCookiesCommand = registerCommand({
|
|
932
1401
|
name: "get-cookies",
|
|
933
1402
|
description: "Get all cookies for the current page",
|
|
934
1403
|
scope: "page",
|
|
935
|
-
result:
|
|
936
|
-
cookies:
|
|
1404
|
+
result: z11.object({
|
|
1405
|
+
cookies: z11.array(z11.record(z11.unknown()))
|
|
937
1406
|
}),
|
|
938
1407
|
handler: async (_p, ctx) => {
|
|
939
1408
|
const cookies = await ctx.browserContext.cookies();
|
|
940
|
-
return
|
|
1409
|
+
return ok11({ cookies });
|
|
941
1410
|
}
|
|
942
1411
|
});
|
|
943
1412
|
var setCookieCommand = registerCommand({
|
|
944
1413
|
name: "set-cookie",
|
|
945
1414
|
description: "Set a cookie",
|
|
946
1415
|
scope: "page",
|
|
947
|
-
parameters:
|
|
948
|
-
name:
|
|
949
|
-
value:
|
|
950
|
-
domain:
|
|
951
|
-
path:
|
|
952
|
-
url:
|
|
953
|
-
expires:
|
|
954
|
-
httpOnly:
|
|
955
|
-
secure:
|
|
956
|
-
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()
|
|
957
1426
|
}),
|
|
958
|
-
result:
|
|
1427
|
+
result: z11.object({ name: z11.string() }),
|
|
959
1428
|
handler: async (p, ctx) => {
|
|
960
1429
|
const cookie = { ...p };
|
|
961
1430
|
if (!cookie.domain && !cookie.url) {
|
|
@@ -970,38 +1439,38 @@ var setCookieCommand = registerCommand({
|
|
|
970
1439
|
}
|
|
971
1440
|
}
|
|
972
1441
|
if (!cookie.domain && !cookie.url) {
|
|
973
|
-
return
|
|
1442
|
+
return fail4("set-cookie requires --domain or --url, or a non-blank page URL to infer from");
|
|
974
1443
|
}
|
|
975
1444
|
await ctx.browserContext.addCookies([cookie]);
|
|
976
|
-
return
|
|
1445
|
+
return ok11({ name: p.name });
|
|
977
1446
|
}
|
|
978
1447
|
});
|
|
979
1448
|
var clearCookiesCommand = registerCommand({
|
|
980
1449
|
name: "clear-cookies",
|
|
981
1450
|
description: "Clear all cookies",
|
|
982
1451
|
scope: "page",
|
|
983
|
-
result:
|
|
1452
|
+
result: z11.object({ cleared: z11.boolean() }),
|
|
984
1453
|
handler: async (_p, ctx) => {
|
|
985
1454
|
await ctx.browserContext.clearCookies();
|
|
986
|
-
return
|
|
1455
|
+
return ok11({ cleared: true });
|
|
987
1456
|
}
|
|
988
1457
|
});
|
|
989
1458
|
var getLocalStorageCommand = registerCommand({
|
|
990
1459
|
name: "get-local-storage",
|
|
991
1460
|
description: "Get localStorage entries",
|
|
992
1461
|
scope: "page",
|
|
993
|
-
parameters:
|
|
994
|
-
key:
|
|
1462
|
+
parameters: z11.object({
|
|
1463
|
+
key: z11.string().optional()
|
|
995
1464
|
}),
|
|
996
|
-
result:
|
|
997
|
-
|
|
998
|
-
|
|
1465
|
+
result: z11.union([
|
|
1466
|
+
z11.object({ key: z11.string(), value: z11.string().nullable() }),
|
|
1467
|
+
z11.object({ data: z11.record(z11.string()) })
|
|
999
1468
|
]),
|
|
1000
1469
|
handler: async (p, ctx) => {
|
|
1001
1470
|
try {
|
|
1002
1471
|
if (p.key) {
|
|
1003
1472
|
const value = await ctx.page.evaluate((k) => localStorage.getItem(k), p.key);
|
|
1004
|
-
return
|
|
1473
|
+
return ok11({ key: p.key, value });
|
|
1005
1474
|
}
|
|
1006
1475
|
const data = await ctx.page.evaluate(() => {
|
|
1007
1476
|
const entries = {};
|
|
@@ -1011,9 +1480,9 @@ var getLocalStorageCommand = registerCommand({
|
|
|
1011
1480
|
}
|
|
1012
1481
|
return entries;
|
|
1013
1482
|
});
|
|
1014
|
-
return
|
|
1483
|
+
return ok11({ data });
|
|
1015
1484
|
} catch (e) {
|
|
1016
|
-
return
|
|
1485
|
+
return fail4(`localStorage not accessible on this page: ${e instanceof Error ? e.message : String(e)}`);
|
|
1017
1486
|
}
|
|
1018
1487
|
}
|
|
1019
1488
|
});
|
|
@@ -1021,11 +1490,11 @@ var setLocalStorageCommand = registerCommand({
|
|
|
1021
1490
|
name: "set-local-storage",
|
|
1022
1491
|
description: "Set a localStorage entry",
|
|
1023
1492
|
scope: "page",
|
|
1024
|
-
parameters:
|
|
1025
|
-
key:
|
|
1026
|
-
value:
|
|
1493
|
+
parameters: z11.object({
|
|
1494
|
+
key: z11.string(),
|
|
1495
|
+
value: z11.string()
|
|
1027
1496
|
}),
|
|
1028
|
-
result:
|
|
1497
|
+
result: z11.object({ key: z11.string() }),
|
|
1029
1498
|
handler: async (p, ctx) => {
|
|
1030
1499
|
try {
|
|
1031
1500
|
await ctx.page.evaluate(
|
|
@@ -1034,9 +1503,9 @@ var setLocalStorageCommand = registerCommand({
|
|
|
1034
1503
|
},
|
|
1035
1504
|
{ key: p.key, value: p.value }
|
|
1036
1505
|
);
|
|
1037
|
-
return
|
|
1506
|
+
return ok11({ key: p.key });
|
|
1038
1507
|
} catch (e) {
|
|
1039
|
-
return
|
|
1508
|
+
return fail4(`localStorage not accessible on this page: ${e instanceof Error ? e.message : String(e)}`);
|
|
1040
1509
|
}
|
|
1041
1510
|
}
|
|
1042
1511
|
});
|
|
@@ -1044,26 +1513,24 @@ var clearLocalStorageCommand = registerCommand({
|
|
|
1044
1513
|
name: "clear-local-storage",
|
|
1045
1514
|
description: "Clear all localStorage entries",
|
|
1046
1515
|
scope: "page",
|
|
1047
|
-
result:
|
|
1516
|
+
result: z11.object({ cleared: z11.boolean() }),
|
|
1048
1517
|
handler: async (_p, ctx) => {
|
|
1049
1518
|
try {
|
|
1050
1519
|
await ctx.page.evaluate(() => localStorage.clear());
|
|
1051
|
-
return
|
|
1520
|
+
return ok11({ cleared: true });
|
|
1052
1521
|
} catch (e) {
|
|
1053
|
-
return
|
|
1522
|
+
return fail4(`localStorage not accessible on this page: ${e instanceof Error ? e.message : String(e)}`);
|
|
1054
1523
|
}
|
|
1055
1524
|
}
|
|
1056
1525
|
});
|
|
1057
1526
|
|
|
1058
1527
|
// src/commands/screenshot.ts
|
|
1059
|
-
import { z as
|
|
1060
|
-
import { ok as
|
|
1528
|
+
import { z as z12 } from "zod";
|
|
1529
|
+
import { ok as ok12, fail as fail5 } from "@dyyz1993/xcli-core";
|
|
1061
1530
|
import { writeFileSync, mkdirSync } from "fs";
|
|
1062
|
-
import { dirname, join as
|
|
1063
|
-
import { homedir as homedir2 } from "os";
|
|
1064
|
-
var SCREENSHOTS_DIR = join2(homedir2(), ".xbrowser", "screenshots");
|
|
1531
|
+
import { dirname, join as join4 } from "path";
|
|
1065
1532
|
function ensureScreenshotsDir() {
|
|
1066
|
-
mkdirSync(
|
|
1533
|
+
mkdirSync(resolveScreenshotsDir(), { recursive: true });
|
|
1067
1534
|
}
|
|
1068
1535
|
function ensureParentDir(filePath) {
|
|
1069
1536
|
const dir = dirname(filePath);
|
|
@@ -1079,30 +1546,30 @@ function generateScreenshotPath(format) {
|
|
|
1079
1546
|
const timestamp = Date.now();
|
|
1080
1547
|
const random = Math.random().toString(36).slice(2, 8);
|
|
1081
1548
|
const ext = format === "jpeg" ? "jpg" : "png";
|
|
1082
|
-
return
|
|
1549
|
+
return join4(resolveScreenshotsDir(), `screenshot-${timestamp}-${random}.${ext}`);
|
|
1083
1550
|
}
|
|
1084
1551
|
var screenshotCommand = registerCommand({
|
|
1085
1552
|
name: "screenshot",
|
|
1086
|
-
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>)",
|
|
1087
1554
|
scope: "page",
|
|
1088
1555
|
selectorParams: ["selector"],
|
|
1089
|
-
parameters:
|
|
1090
|
-
selector:
|
|
1091
|
-
type:
|
|
1092
|
-
fullPage:
|
|
1093
|
-
output:
|
|
1094
|
-
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")
|
|
1095
1562
|
}),
|
|
1096
|
-
result:
|
|
1097
|
-
|
|
1098
|
-
data:
|
|
1099
|
-
format:
|
|
1100
|
-
size:
|
|
1563
|
+
result: z12.union([
|
|
1564
|
+
z12.object({
|
|
1565
|
+
data: z12.string(),
|
|
1566
|
+
format: z12.string(),
|
|
1567
|
+
size: z12.number()
|
|
1101
1568
|
}),
|
|
1102
|
-
|
|
1103
|
-
output:
|
|
1104
|
-
format:
|
|
1105
|
-
size:
|
|
1569
|
+
z12.object({
|
|
1570
|
+
output: z12.string(),
|
|
1571
|
+
format: z12.string(),
|
|
1572
|
+
size: z12.number()
|
|
1106
1573
|
})
|
|
1107
1574
|
]),
|
|
1108
1575
|
handler: async (p, ctx) => {
|
|
@@ -1120,21 +1587,21 @@ var screenshotCommand = registerCommand({
|
|
|
1120
1587
|
if (p.output) {
|
|
1121
1588
|
const dirErr = ensureParentDir(p.output);
|
|
1122
1589
|
if (dirErr) {
|
|
1123
|
-
return
|
|
1590
|
+
return fail5(`Cannot create directory for --output "${p.output}": ${dirErr}`);
|
|
1124
1591
|
}
|
|
1125
1592
|
try {
|
|
1126
1593
|
writeFileSync(p.output, buffer, "binary");
|
|
1127
1594
|
} catch (err) {
|
|
1128
|
-
return
|
|
1595
|
+
return fail5(`Failed to write screenshot to "${p.output}": ${err instanceof Error ? err.message : String(err)}`);
|
|
1129
1596
|
}
|
|
1130
|
-
return
|
|
1597
|
+
return ok12({
|
|
1131
1598
|
output: p.output,
|
|
1132
1599
|
format,
|
|
1133
1600
|
size: buffer.length
|
|
1134
1601
|
});
|
|
1135
1602
|
}
|
|
1136
1603
|
if (p.base64) {
|
|
1137
|
-
return
|
|
1604
|
+
return ok12({
|
|
1138
1605
|
data: buffer.toString("base64"),
|
|
1139
1606
|
format,
|
|
1140
1607
|
size: buffer.length
|
|
@@ -1143,7 +1610,7 @@ var screenshotCommand = registerCommand({
|
|
|
1143
1610
|
ensureScreenshotsDir();
|
|
1144
1611
|
const screenshotPath = generateScreenshotPath(format);
|
|
1145
1612
|
writeFileSync(screenshotPath, buffer, "binary");
|
|
1146
|
-
return
|
|
1613
|
+
return ok12({
|
|
1147
1614
|
output: screenshotPath,
|
|
1148
1615
|
format,
|
|
1149
1616
|
size: buffer.length
|
|
@@ -1152,19 +1619,19 @@ var screenshotCommand = registerCommand({
|
|
|
1152
1619
|
});
|
|
1153
1620
|
|
|
1154
1621
|
// src/commands/structure.ts
|
|
1155
|
-
import { z as
|
|
1156
|
-
import { ok as
|
|
1622
|
+
import { z as z13 } from "zod";
|
|
1623
|
+
import { ok as ok13 } from "@dyyz1993/xcli-core";
|
|
1157
1624
|
var structureCommand = registerCommand({
|
|
1158
1625
|
name: "structure",
|
|
1159
1626
|
description: "Get the DOM structure of the page or an element",
|
|
1160
1627
|
scope: "page",
|
|
1161
1628
|
selectorParams: ["selector"],
|
|
1162
|
-
parameters:
|
|
1163
|
-
selector:
|
|
1164
|
-
depth:
|
|
1629
|
+
parameters: z13.object({
|
|
1630
|
+
selector: z13.string().optional(),
|
|
1631
|
+
depth: z13.number().optional()
|
|
1165
1632
|
}),
|
|
1166
|
-
result:
|
|
1167
|
-
structure:
|
|
1633
|
+
result: z13.object({
|
|
1634
|
+
structure: z13.record(z13.unknown())
|
|
1168
1635
|
}),
|
|
1169
1636
|
handler: async (p, ctx) => {
|
|
1170
1637
|
const structure = await ctx.page.evaluate(
|
|
@@ -1196,30 +1663,30 @@ var structureCommand = registerCommand({
|
|
|
1196
1663
|
},
|
|
1197
1664
|
{ sel: p.selector || "body", maxDepth: p.depth || 5 }
|
|
1198
1665
|
);
|
|
1199
|
-
return
|
|
1666
|
+
return ok13({ structure });
|
|
1200
1667
|
}
|
|
1201
1668
|
});
|
|
1202
1669
|
|
|
1203
1670
|
// src/commands/viewport.ts
|
|
1204
|
-
import { z as
|
|
1205
|
-
import { ok as
|
|
1671
|
+
import { z as z14 } from "zod";
|
|
1672
|
+
import { ok as ok14 } from "@dyyz1993/xcli-core";
|
|
1206
1673
|
var setViewportCommand = registerCommand({
|
|
1207
1674
|
name: "set-viewport",
|
|
1208
1675
|
description: "Set the viewport size and properties",
|
|
1209
1676
|
scope: "browser",
|
|
1210
|
-
parameters:
|
|
1211
|
-
width:
|
|
1212
|
-
height:
|
|
1213
|
-
deviceScaleFactor:
|
|
1214
|
-
isMobile:
|
|
1215
|
-
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()
|
|
1216
1683
|
}),
|
|
1217
|
-
result:
|
|
1218
|
-
width:
|
|
1219
|
-
height:
|
|
1220
|
-
deviceScaleFactor:
|
|
1221
|
-
isMobile:
|
|
1222
|
-
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()
|
|
1223
1690
|
}),
|
|
1224
1691
|
handler: async (p, ctx) => {
|
|
1225
1692
|
const viewport = ctx.page.viewportSize();
|
|
@@ -1232,7 +1699,7 @@ var setViewportCommand = registerCommand({
|
|
|
1232
1699
|
...p.isMobile !== void 0 && { isMobile: p.isMobile },
|
|
1233
1700
|
...p.hasTouch !== void 0 && { hasTouch: p.hasTouch }
|
|
1234
1701
|
});
|
|
1235
|
-
return
|
|
1702
|
+
return ok14({
|
|
1236
1703
|
width,
|
|
1237
1704
|
height,
|
|
1238
1705
|
...p.deviceScaleFactor !== void 0 && { deviceScaleFactor: p.deviceScaleFactor },
|
|
@@ -1243,17 +1710,17 @@ var setViewportCommand = registerCommand({
|
|
|
1243
1710
|
});
|
|
1244
1711
|
|
|
1245
1712
|
// src/commands/frame.ts
|
|
1246
|
-
import { z as
|
|
1247
|
-
import { ok as
|
|
1713
|
+
import { z as z15 } from "zod";
|
|
1714
|
+
import { ok as ok15, fail as fail6 } from "@dyyz1993/xcli-core";
|
|
1248
1715
|
var framesCommand = registerCommand({
|
|
1249
1716
|
name: "frames",
|
|
1250
1717
|
description: "List all frames in the current page",
|
|
1251
1718
|
scope: "page",
|
|
1252
|
-
result:
|
|
1253
|
-
frames:
|
|
1254
|
-
index:
|
|
1255
|
-
name:
|
|
1256
|
-
url:
|
|
1719
|
+
result: z15.object({
|
|
1720
|
+
frames: z15.array(z15.object({
|
|
1721
|
+
index: z15.number(),
|
|
1722
|
+
name: z15.string().nullable(),
|
|
1723
|
+
url: z15.string()
|
|
1257
1724
|
}))
|
|
1258
1725
|
}),
|
|
1259
1726
|
handler: async (_p, ctx) => {
|
|
@@ -1264,21 +1731,21 @@ var framesCommand = registerCommand({
|
|
|
1264
1731
|
name: frame.name(),
|
|
1265
1732
|
url: frame.url()
|
|
1266
1733
|
}));
|
|
1267
|
-
return
|
|
1734
|
+
return ok15({ frames: frameList });
|
|
1268
1735
|
}
|
|
1269
1736
|
});
|
|
1270
1737
|
var frameCommand = registerCommand({
|
|
1271
1738
|
name: "frame",
|
|
1272
1739
|
description: "Get frame info by index or name",
|
|
1273
1740
|
scope: "page",
|
|
1274
|
-
parameters:
|
|
1275
|
-
index:
|
|
1276
|
-
name:
|
|
1741
|
+
parameters: z15.object({
|
|
1742
|
+
index: z15.number().int().min(0).optional(),
|
|
1743
|
+
name: z15.string().optional()
|
|
1277
1744
|
}),
|
|
1278
|
-
result:
|
|
1279
|
-
name:
|
|
1280
|
-
url:
|
|
1281
|
-
error:
|
|
1745
|
+
result: z15.object({
|
|
1746
|
+
name: z15.string().nullable(),
|
|
1747
|
+
url: z15.string(),
|
|
1748
|
+
error: z15.string().optional()
|
|
1282
1749
|
}),
|
|
1283
1750
|
handler: async (p, ctx) => {
|
|
1284
1751
|
const discover = ctx.page.discoverFrames;
|
|
@@ -1289,12 +1756,12 @@ var frameCommand = registerCommand({
|
|
|
1289
1756
|
} else if (p.name !== void 0) {
|
|
1290
1757
|
targetFrame = rawFrames.find((f) => f.name() === p.name);
|
|
1291
1758
|
} else {
|
|
1292
|
-
return
|
|
1759
|
+
return fail6("Must provide index or name");
|
|
1293
1760
|
}
|
|
1294
1761
|
if (!targetFrame) {
|
|
1295
|
-
return
|
|
1762
|
+
return fail6("Frame not found");
|
|
1296
1763
|
}
|
|
1297
|
-
return
|
|
1764
|
+
return ok15({
|
|
1298
1765
|
name: targetFrame.name(),
|
|
1299
1766
|
url: targetFrame.url()
|
|
1300
1767
|
});
|
|
@@ -1302,27 +1769,27 @@ var frameCommand = registerCommand({
|
|
|
1302
1769
|
});
|
|
1303
1770
|
|
|
1304
1771
|
// src/commands/ui-debug.ts
|
|
1305
|
-
import { z as
|
|
1306
|
-
import { ok as
|
|
1772
|
+
import { z as z16 } from "zod";
|
|
1773
|
+
import { ok as ok16 } from "@dyyz1993/xcli-core";
|
|
1307
1774
|
var consoleCheckCommand = registerCommand({
|
|
1308
1775
|
name: "console",
|
|
1309
1776
|
description: "Collect and analyze browser console messages (errors, warnings, logs)",
|
|
1310
1777
|
scope: "page",
|
|
1311
|
-
parameters:
|
|
1312
|
-
url:
|
|
1313
|
-
duration:
|
|
1314
|
-
filter:
|
|
1315
|
-
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)
|
|
1316
1783
|
}),
|
|
1317
|
-
result:
|
|
1318
|
-
url:
|
|
1319
|
-
duration:
|
|
1320
|
-
total:
|
|
1321
|
-
errors:
|
|
1322
|
-
warnings:
|
|
1323
|
-
messages:
|
|
1324
|
-
summary:
|
|
1325
|
-
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()
|
|
1326
1793
|
}),
|
|
1327
1794
|
handler: async (p, ctx) => {
|
|
1328
1795
|
const { page } = ctx;
|
|
@@ -1404,7 +1871,7 @@ ${a.stack || ""}`;
|
|
|
1404
1871
|
const filtered = p.filter === "all" ? messages : messages.filter((m) => m.type === p.filter);
|
|
1405
1872
|
const errorCount = messages.filter((m) => m.type === "error").length;
|
|
1406
1873
|
const warnCount = messages.filter((m) => m.type === "warning").length;
|
|
1407
|
-
return
|
|
1874
|
+
return ok16({
|
|
1408
1875
|
url: page.url(),
|
|
1409
1876
|
duration: p.duration,
|
|
1410
1877
|
total: messages.length,
|
|
@@ -1420,23 +1887,23 @@ var networkCheckCommand = registerCommand({
|
|
|
1420
1887
|
name: "net-debug",
|
|
1421
1888
|
description: "Monitor and analyze network requests \u2014 capture failed requests, slow responses, status codes",
|
|
1422
1889
|
scope: "page",
|
|
1423
|
-
parameters:
|
|
1424
|
-
url:
|
|
1425
|
-
duration:
|
|
1426
|
-
filter:
|
|
1427
|
-
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)')
|
|
1428
1895
|
}),
|
|
1429
|
-
result:
|
|
1430
|
-
url:
|
|
1431
|
-
duration:
|
|
1432
|
-
totalRequests:
|
|
1433
|
-
failedRequests:
|
|
1434
|
-
slowRequests:
|
|
1435
|
-
errorRequests:
|
|
1436
|
-
totalSizeKB:
|
|
1437
|
-
requests:
|
|
1438
|
-
summary:
|
|
1439
|
-
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()
|
|
1440
1907
|
}),
|
|
1441
1908
|
handler: async (p, ctx) => {
|
|
1442
1909
|
const { page } = ctx;
|
|
@@ -1518,7 +1985,7 @@ var networkCheckCommand = registerCommand({
|
|
|
1518
1985
|
const slowCount = requests.filter((r) => r.duration >= p.slowThreshold).length;
|
|
1519
1986
|
const errorCount = requests.filter((r) => r.error).length;
|
|
1520
1987
|
const totalSize = requests.reduce((sum, r) => sum + r.size, 0);
|
|
1521
|
-
return
|
|
1988
|
+
return ok16({
|
|
1522
1989
|
url: page.url(),
|
|
1523
1990
|
duration: p.duration,
|
|
1524
1991
|
totalRequests: requests.length,
|
|
@@ -1540,17 +2007,17 @@ var perfCheckCommand = registerCommand({
|
|
|
1540
2007
|
name: "perf",
|
|
1541
2008
|
description: "Audit page performance metrics \u2014 load time, FCP, LCP, CLS, TTFB, resource sizes",
|
|
1542
2009
|
scope: "page",
|
|
1543
|
-
parameters:
|
|
1544
|
-
url:
|
|
1545
|
-
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")
|
|
1546
2013
|
}),
|
|
1547
|
-
result:
|
|
1548
|
-
url:
|
|
1549
|
-
iterations:
|
|
1550
|
-
metrics:
|
|
1551
|
-
allIterations:
|
|
1552
|
-
passed:
|
|
1553
|
-
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()
|
|
1554
2021
|
}),
|
|
1555
2022
|
handler: async (p, ctx) => {
|
|
1556
2023
|
const { page } = ctx;
|
|
@@ -1612,7 +2079,7 @@ var perfCheckCommand = registerCommand({
|
|
|
1612
2079
|
if (resourceStats) avg.resourceStats = resourceStats;
|
|
1613
2080
|
return avg;
|
|
1614
2081
|
})();
|
|
1615
|
-
return
|
|
2082
|
+
return ok16({
|
|
1616
2083
|
url: page.url(),
|
|
1617
2084
|
iterations: p.iterations,
|
|
1618
2085
|
metrics: avgMetrics,
|
|
@@ -1626,23 +2093,23 @@ var healthCheckCommand = registerCommand({
|
|
|
1626
2093
|
name: "health",
|
|
1627
2094
|
description: "Comprehensive page health check \u2014 broken links, missing images, console errors, SEO issues",
|
|
1628
2095
|
scope: "page",
|
|
1629
|
-
parameters:
|
|
1630
|
-
url:
|
|
1631
|
-
checkLinks:
|
|
1632
|
-
checkImages:
|
|
1633
|
-
checkMeta:
|
|
1634
|
-
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")
|
|
1635
2102
|
}),
|
|
1636
|
-
result:
|
|
1637
|
-
url:
|
|
1638
|
-
title:
|
|
1639
|
-
passed:
|
|
1640
|
-
totalIssues:
|
|
1641
|
-
errors:
|
|
1642
|
-
warnings:
|
|
1643
|
-
info:
|
|
1644
|
-
issues:
|
|
1645
|
-
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()
|
|
1646
2113
|
}),
|
|
1647
2114
|
handler: async (p, ctx) => {
|
|
1648
2115
|
const { page } = ctx;
|
|
@@ -1729,7 +2196,7 @@ var healthCheckCommand = registerCommand({
|
|
|
1729
2196
|
}, { checkLinks: p.checkLinks, checkImages: p.checkImages, checkMeta: p.checkMeta, maxLinks: p.maxLinks });
|
|
1730
2197
|
const errors = result.issues.filter((i) => i.severity === "error").length;
|
|
1731
2198
|
const warnings = result.issues.filter((i) => i.severity === "warning").length;
|
|
1732
|
-
return
|
|
2199
|
+
return ok16({
|
|
1733
2200
|
url: result.url,
|
|
1734
2201
|
title: result.title,
|
|
1735
2202
|
passed: errors === 0,
|
|
@@ -1744,68 +2211,66 @@ var healthCheckCommand = registerCommand({
|
|
|
1744
2211
|
});
|
|
1745
2212
|
|
|
1746
2213
|
// src/commands/actions.ts
|
|
1747
|
-
import { z as
|
|
1748
|
-
import { ok as
|
|
2214
|
+
import { z as z17 } from "zod";
|
|
2215
|
+
import { ok as ok17 } from "@dyyz1993/xcli-core";
|
|
1749
2216
|
import { writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
1750
|
-
import { join as
|
|
1751
|
-
import { homedir as homedir3 } from "os";
|
|
1752
|
-
var SCREENSHOTS_DIR2 = join3(homedir3(), ".xbrowser", "screenshots");
|
|
2217
|
+
import { join as join5 } from "path";
|
|
1753
2218
|
function ensureScreenshotsDir2() {
|
|
1754
|
-
mkdirSync2(
|
|
2219
|
+
mkdirSync2(resolveScreenshotsDir(), { recursive: true });
|
|
1755
2220
|
}
|
|
1756
2221
|
function generateScreenshotPath2(format) {
|
|
1757
2222
|
const timestamp = Date.now();
|
|
1758
2223
|
const random = Math.random().toString(36).slice(2, 8);
|
|
1759
2224
|
const ext = format === "jpeg" ? "jpg" : "png";
|
|
1760
|
-
return
|
|
2225
|
+
return join5(resolveScreenshotsDir(), `screenshot-${timestamp}-${random}.${ext}`);
|
|
1761
2226
|
}
|
|
1762
|
-
var waitActionSchema =
|
|
1763
|
-
type:
|
|
1764
|
-
milliseconds:
|
|
1765
|
-
selector:
|
|
2227
|
+
var waitActionSchema = z17.object({
|
|
2228
|
+
type: z17.literal("wait"),
|
|
2229
|
+
milliseconds: z17.number().positive().optional(),
|
|
2230
|
+
selector: z17.string().optional()
|
|
1766
2231
|
}).refine(
|
|
1767
2232
|
(d) => (d.milliseconds !== void 0 || d.selector !== void 0) && !(d.milliseconds !== void 0 && d.selector !== void 0),
|
|
1768
2233
|
{ message: "Either 'milliseconds' or 'selector' must be provided, but not both." }
|
|
1769
2234
|
);
|
|
1770
|
-
var clickActionSchema =
|
|
1771
|
-
type:
|
|
1772
|
-
selector:
|
|
1773
|
-
all:
|
|
2235
|
+
var clickActionSchema = z17.object({
|
|
2236
|
+
type: z17.literal("click"),
|
|
2237
|
+
selector: z17.string(),
|
|
2238
|
+
all: z17.boolean().optional()
|
|
1774
2239
|
});
|
|
1775
|
-
var screenshotActionSchema =
|
|
1776
|
-
type:
|
|
1777
|
-
fullPage:
|
|
1778
|
-
quality:
|
|
1779
|
-
viewport:
|
|
1780
|
-
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")
|
|
1781
2246
|
});
|
|
1782
|
-
var writeActionSchema =
|
|
1783
|
-
type:
|
|
1784
|
-
text:
|
|
2247
|
+
var writeActionSchema = z17.object({
|
|
2248
|
+
type: z17.literal("write"),
|
|
2249
|
+
text: z17.string()
|
|
1785
2250
|
});
|
|
1786
|
-
var pressActionSchema =
|
|
1787
|
-
type:
|
|
1788
|
-
key:
|
|
2251
|
+
var pressActionSchema = z17.object({
|
|
2252
|
+
type: z17.literal("press"),
|
|
2253
|
+
key: z17.string()
|
|
1789
2254
|
});
|
|
1790
|
-
var scrollActionSchema =
|
|
1791
|
-
type:
|
|
1792
|
-
direction:
|
|
1793
|
-
selector:
|
|
2255
|
+
var scrollActionSchema = z17.object({
|
|
2256
|
+
type: z17.literal("scroll"),
|
|
2257
|
+
direction: z17.enum(["up", "down"]).optional(),
|
|
2258
|
+
selector: z17.string().optional()
|
|
1794
2259
|
});
|
|
1795
|
-
var scrapeActionSchema =
|
|
1796
|
-
type:
|
|
2260
|
+
var scrapeActionSchema = z17.object({
|
|
2261
|
+
type: z17.literal("scrape")
|
|
1797
2262
|
});
|
|
1798
|
-
var executeJavascriptActionSchema =
|
|
1799
|
-
type:
|
|
1800
|
-
script:
|
|
2263
|
+
var executeJavascriptActionSchema = z17.object({
|
|
2264
|
+
type: z17.literal("executeJavascript"),
|
|
2265
|
+
script: z17.string()
|
|
1801
2266
|
});
|
|
1802
|
-
var pdfActionSchema =
|
|
1803
|
-
type:
|
|
1804
|
-
landscape:
|
|
1805
|
-
scale:
|
|
1806
|
-
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()
|
|
1807
2272
|
});
|
|
1808
|
-
var actionSchema =
|
|
2273
|
+
var actionSchema = z17.union([
|
|
1809
2274
|
waitActionSchema,
|
|
1810
2275
|
clickActionSchema,
|
|
1811
2276
|
screenshotActionSchema,
|
|
@@ -1896,11 +2361,11 @@ var actionsCommand = registerCommand({
|
|
|
1896
2361
|
name: "actions",
|
|
1897
2362
|
description: "Execute a sequence of actions (wait, click, scroll, screenshot, fill, etc.)",
|
|
1898
2363
|
scope: "page",
|
|
1899
|
-
parameters:
|
|
1900
|
-
url:
|
|
1901
|
-
actions:
|
|
1902
|
-
output:
|
|
1903
|
-
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)")
|
|
1904
2369
|
}),
|
|
1905
2370
|
handler: async (p, ctx) => {
|
|
1906
2371
|
await ctx.page.goto(p.url, { waitUntil: "domcontentloaded" });
|
|
@@ -1920,14 +2385,14 @@ var actionsCommand = registerCommand({
|
|
|
1920
2385
|
const finalUrl = ctx.page.url();
|
|
1921
2386
|
const timedOut = results.length < p.actions.length;
|
|
1922
2387
|
if (p.output === "text") {
|
|
1923
|
-
return
|
|
2388
|
+
return ok17({
|
|
1924
2389
|
title,
|
|
1925
2390
|
url: finalUrl,
|
|
1926
2391
|
actions: results.map((r) => JSON.stringify(r)).join("\n"),
|
|
1927
2392
|
...timedOut ? { warning: `Timed out after ${p.timeout}s, completed ${results.length}/${p.actions.length} actions` } : {}
|
|
1928
2393
|
});
|
|
1929
2394
|
}
|
|
1930
|
-
return
|
|
2395
|
+
return ok17({
|
|
1931
2396
|
title,
|
|
1932
2397
|
url: finalUrl,
|
|
1933
2398
|
results,
|
|
@@ -1937,8 +2402,8 @@ var actionsCommand = registerCommand({
|
|
|
1937
2402
|
});
|
|
1938
2403
|
|
|
1939
2404
|
// src/commands/scrape.ts
|
|
1940
|
-
import { z as
|
|
1941
|
-
import { ok as
|
|
2405
|
+
import { z as z18 } from "zod";
|
|
2406
|
+
import { ok as ok18, fail as fail7 } from "@dyyz1993/xcli-core";
|
|
1942
2407
|
|
|
1943
2408
|
// src/lib/html-to-markdown.ts
|
|
1944
2409
|
import * as cheerio from "cheerio";
|
|
@@ -2323,15 +2788,15 @@ var scrapeCommand = registerCommand({
|
|
|
2323
2788
|
description: "Scrape a page and convert to Markdown (with JS rendering)",
|
|
2324
2789
|
scope: "project",
|
|
2325
2790
|
selectorParams: ["selector"],
|
|
2326
|
-
parameters:
|
|
2327
|
-
url:
|
|
2328
|
-
selector:
|
|
2329
|
-
timeout:
|
|
2330
|
-
format:
|
|
2331
|
-
onlyMainContent:
|
|
2332
|
-
retries:
|
|
2333
|
-
waitAfterLoad:
|
|
2334
|
-
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")
|
|
2335
2800
|
}),
|
|
2336
2801
|
handler: async (p, ctx) => {
|
|
2337
2802
|
const { context, page } = await createEphemeralContext(resolveLaunchOpts(ctx));
|
|
@@ -2339,7 +2804,7 @@ var scrapeCommand = registerCommand({
|
|
|
2339
2804
|
try {
|
|
2340
2805
|
const targetUrl = p.url || page.url();
|
|
2341
2806
|
if (!targetUrl || targetUrl === "about:blank") {
|
|
2342
|
-
return
|
|
2807
|
+
return fail7("URL is required. Provide a URL or connect to a browser (--cdp) with a non-blank page.");
|
|
2343
2808
|
}
|
|
2344
2809
|
let lastError;
|
|
2345
2810
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
@@ -2409,7 +2874,7 @@ var scrapeCommand = registerCommand({
|
|
|
2409
2874
|
} catch {
|
|
2410
2875
|
}
|
|
2411
2876
|
if (p.mode === "smart") {
|
|
2412
|
-
return
|
|
2877
|
+
return ok18(structured);
|
|
2413
2878
|
}
|
|
2414
2879
|
if (p.mode === "compact") {
|
|
2415
2880
|
const compactData = structured.tables.length > 0 ? structured.tables.map((t) => ({
|
|
@@ -2422,9 +2887,9 @@ var scrapeCommand = registerCommand({
|
|
|
2422
2887
|
return compact;
|
|
2423
2888
|
})
|
|
2424
2889
|
})) : structured.mainText?.substring(0, 500);
|
|
2425
|
-
return
|
|
2890
|
+
return ok18({ url: structured.url, title: structured.title, data: compactData });
|
|
2426
2891
|
}
|
|
2427
|
-
return
|
|
2892
|
+
return ok18(structured);
|
|
2428
2893
|
}
|
|
2429
2894
|
let content;
|
|
2430
2895
|
switch (p.format) {
|
|
@@ -2483,7 +2948,7 @@ var scrapeCommand = registerCommand({
|
|
|
2483
2948
|
content = await page.innerText("body");
|
|
2484
2949
|
break;
|
|
2485
2950
|
}
|
|
2486
|
-
return
|
|
2951
|
+
return ok18({ content, title, url: finalUrl });
|
|
2487
2952
|
} catch (err) {
|
|
2488
2953
|
lastError = err instanceof Error ? err : new Error(String(err));
|
|
2489
2954
|
if (attempt < maxAttempts) {
|
|
@@ -2493,20 +2958,20 @@ var scrapeCommand = registerCommand({
|
|
|
2493
2958
|
}
|
|
2494
2959
|
}
|
|
2495
2960
|
}
|
|
2496
|
-
return
|
|
2961
|
+
return fail7(`Scrape failed after ${maxAttempts} attempt(s): ${lastError?.message ?? "unknown error"}`);
|
|
2497
2962
|
} finally {
|
|
2498
2963
|
await closeEphemeralContext(context);
|
|
2499
2964
|
}
|
|
2500
2965
|
},
|
|
2501
|
-
result:
|
|
2502
|
-
url:
|
|
2503
|
-
title:
|
|
2966
|
+
result: z18.object({
|
|
2967
|
+
url: z18.string(),
|
|
2968
|
+
title: z18.string()
|
|
2504
2969
|
}).passthrough()
|
|
2505
2970
|
});
|
|
2506
2971
|
|
|
2507
2972
|
// src/commands/map.ts
|
|
2508
|
-
import { z as
|
|
2509
|
-
import { ok as
|
|
2973
|
+
import { z as z19 } from "zod";
|
|
2974
|
+
import { ok as ok19, fail as fail8 } from "@dyyz1993/xcli-core";
|
|
2510
2975
|
|
|
2511
2976
|
// src/utils/url.ts
|
|
2512
2977
|
var SKIP_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
@@ -2741,21 +3206,21 @@ var mapCommand = registerCommand({
|
|
|
2741
3206
|
name: "map",
|
|
2742
3207
|
description: "Discover all URLs on a website via sitemap and page link extraction",
|
|
2743
3208
|
scope: "project",
|
|
2744
|
-
parameters:
|
|
2745
|
-
url:
|
|
2746
|
-
search:
|
|
2747
|
-
sitemap:
|
|
2748
|
-
includeSubdomains:
|
|
2749
|
-
allowExternalLinks:
|
|
2750
|
-
limit:
|
|
2751
|
-
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")
|
|
2752
3217
|
}),
|
|
2753
3218
|
handler: async (p, ctx) => {
|
|
2754
3219
|
const { context, page } = await createEphemeralContext(resolveLaunchOpts(ctx));
|
|
2755
3220
|
try {
|
|
2756
3221
|
const targetUrl = p.url || page.url();
|
|
2757
3222
|
if (!targetUrl || targetUrl === "about:blank") {
|
|
2758
|
-
return
|
|
3223
|
+
return fail8("URL is required. Provide a URL or connect to a browser (--cdp) with a non-blank page.");
|
|
2759
3224
|
}
|
|
2760
3225
|
const links = await discoverUrls(page, targetUrl, {
|
|
2761
3226
|
sitemap: p.sitemap,
|
|
@@ -2766,7 +3231,7 @@ var mapCommand = registerCommand({
|
|
|
2766
3231
|
verbose: p.verbose
|
|
2767
3232
|
});
|
|
2768
3233
|
const linkObjects = links.map((url) => ({ url }));
|
|
2769
|
-
return
|
|
3234
|
+
return ok19({
|
|
2770
3235
|
links: linkObjects,
|
|
2771
3236
|
success: true
|
|
2772
3237
|
});
|
|
@@ -2774,15 +3239,15 @@ var mapCommand = registerCommand({
|
|
|
2774
3239
|
await closeEphemeralContext(context);
|
|
2775
3240
|
}
|
|
2776
3241
|
},
|
|
2777
|
-
result:
|
|
2778
|
-
links:
|
|
2779
|
-
success:
|
|
3242
|
+
result: z19.object({
|
|
3243
|
+
links: z19.array(z19.object({ url: z19.string() })),
|
|
3244
|
+
success: z19.boolean()
|
|
2780
3245
|
})
|
|
2781
3246
|
});
|
|
2782
3247
|
|
|
2783
3248
|
// src/commands/crawl.ts
|
|
2784
|
-
import { z as
|
|
2785
|
-
import { ok as
|
|
3249
|
+
import { z as z20 } from "zod";
|
|
3250
|
+
import { ok as ok20 } from "@dyyz1993/xcli-core";
|
|
2786
3251
|
function stripHashAnchorQuery(url) {
|
|
2787
3252
|
try {
|
|
2788
3253
|
const parsed = new URL(url);
|
|
@@ -2959,21 +3424,21 @@ var crawlCommand = registerCommand({
|
|
|
2959
3424
|
name: "crawl",
|
|
2960
3425
|
description: "Crawl a website and extract content from all pages",
|
|
2961
3426
|
scope: "project",
|
|
2962
|
-
parameters:
|
|
2963
|
-
url:
|
|
2964
|
-
limit:
|
|
2965
|
-
maxDepth:
|
|
2966
|
-
includePaths:
|
|
2967
|
-
excludePaths:
|
|
2968
|
-
allowSubdomains:
|
|
2969
|
-
allowExternalLinks:
|
|
2970
|
-
allowBackwardCrawling:
|
|
2971
|
-
enableSpa:
|
|
2972
|
-
format:
|
|
2973
|
-
onlyMainContent:
|
|
2974
|
-
concurrency:
|
|
2975
|
-
retries:
|
|
2976
|
-
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)
|
|
2977
3442
|
}),
|
|
2978
3443
|
handler: async (p, ctx) => {
|
|
2979
3444
|
const startUrl = new URL(p.url);
|
|
@@ -3112,7 +3577,7 @@ var crawlCommand = registerCommand({
|
|
|
3112
3577
|
if (errorPages.length > 0) {
|
|
3113
3578
|
response.errors = errorPages;
|
|
3114
3579
|
}
|
|
3115
|
-
return
|
|
3580
|
+
return ok20(response);
|
|
3116
3581
|
} finally {
|
|
3117
3582
|
for (const ctx2 of contexts) {
|
|
3118
3583
|
await ctx2.close().catch(() => {
|
|
@@ -3122,15 +3587,15 @@ var crawlCommand = registerCommand({
|
|
|
3122
3587
|
} catch (err) {
|
|
3123
3588
|
const message = err instanceof Error ? err.message : String(err);
|
|
3124
3589
|
const successPages = results.filter((r) => !isPageError(r));
|
|
3125
|
-
return
|
|
3590
|
+
return ok20({ pages: successPages, total: successPages.length, success: false, error: message });
|
|
3126
3591
|
}
|
|
3127
3592
|
}
|
|
3128
3593
|
});
|
|
3129
3594
|
|
|
3130
3595
|
// src/commands/search.ts
|
|
3131
|
-
import { z as
|
|
3596
|
+
import { z as z21 } from "zod";
|
|
3132
3597
|
import * as cheerio2 from "cheerio";
|
|
3133
|
-
import { ok as
|
|
3598
|
+
import { ok as ok21 } from "@dyyz1993/xcli-core";
|
|
3134
3599
|
function getRecencyParams(recency) {
|
|
3135
3600
|
const now = Math.floor(Date.now() / 1e3);
|
|
3136
3601
|
switch (recency) {
|
|
@@ -3464,16 +3929,16 @@ var searchCommand = registerCommand({
|
|
|
3464
3929
|
name: "search",
|
|
3465
3930
|
description: "Search the web and extract results with engine fallback",
|
|
3466
3931
|
scope: "project",
|
|
3467
|
-
parameters:
|
|
3468
|
-
query:
|
|
3469
|
-
engine:
|
|
3470
|
-
limit:
|
|
3471
|
-
full:
|
|
3472
|
-
format:
|
|
3473
|
-
timeout:
|
|
3474
|
-
recency:
|
|
3475
|
-
fallback:
|
|
3476
|
-
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)")
|
|
3477
3942
|
}),
|
|
3478
3943
|
handler: async (p, ctx) => {
|
|
3479
3944
|
const { context } = await createEphemeralContext(resolveLaunchOpts(ctx));
|
|
@@ -3579,7 +4044,7 @@ ${errors.map((e) => ` - ${e.engine}: ${e.error}`).join("\n")}`
|
|
|
3579
4044
|
lines.push(`> ${r.snippet}`);
|
|
3580
4045
|
lines.push("");
|
|
3581
4046
|
}
|
|
3582
|
-
return
|
|
4047
|
+
return ok21({ ...searchResult, content: lines.join("\n") });
|
|
3583
4048
|
}
|
|
3584
4049
|
if (p.format === "text") {
|
|
3585
4050
|
const lines = [`Search: ${searchResult.query} (Engine: ${searchResult.engine}, Total: ${searchResult.total})`, ""];
|
|
@@ -3589,9 +4054,9 @@ ${errors.map((e) => ` - ${e.engine}: ${e.error}`).join("\n")}`
|
|
|
3589
4054
|
lines.push(` ${r.snippet}`);
|
|
3590
4055
|
lines.push("");
|
|
3591
4056
|
}
|
|
3592
|
-
return
|
|
4057
|
+
return ok21({ ...searchResult, content: lines.join("\n") });
|
|
3593
4058
|
}
|
|
3594
|
-
return
|
|
4059
|
+
return ok21(searchResult);
|
|
3595
4060
|
} finally {
|
|
3596
4061
|
await closeEphemeralContext(context);
|
|
3597
4062
|
}
|
|
@@ -3599,8 +4064,8 @@ ${errors.map((e) => ` - ${e.engine}: ${e.error}`).join("\n")}`
|
|
|
3599
4064
|
});
|
|
3600
4065
|
|
|
3601
4066
|
// src/commands/network.ts
|
|
3602
|
-
import { z as
|
|
3603
|
-
import { ok as
|
|
4067
|
+
import { z as z22 } from "zod";
|
|
4068
|
+
import { ok as ok22, fail as fail9 } from "@dyyz1993/xcli-core";
|
|
3604
4069
|
function extractPath2(url) {
|
|
3605
4070
|
try {
|
|
3606
4071
|
const u = new URL(url);
|
|
@@ -3698,19 +4163,19 @@ var networkCommand = registerCommand({
|
|
|
3698
4163
|
name: "network",
|
|
3699
4164
|
description: "Capture and filter network responses from a URL",
|
|
3700
4165
|
scope: "project",
|
|
3701
|
-
parameters:
|
|
3702
|
-
url:
|
|
3703
|
-
filter:
|
|
3704
|
-
match:
|
|
3705
|
-
search:
|
|
3706
|
-
console:
|
|
3707
|
-
timeout:
|
|
3708
|
-
wait:
|
|
3709
|
-
limit:
|
|
3710
|
-
format:
|
|
3711
|
-
ws:
|
|
3712
|
-
listen:
|
|
3713
|
-
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")
|
|
3714
4179
|
}),
|
|
3715
4180
|
handler: async (p, ctx) => {
|
|
3716
4181
|
const startTime = Date.now();
|
|
@@ -3765,7 +4230,7 @@ var networkCommand = registerCommand({
|
|
|
3765
4230
|
};
|
|
3766
4231
|
if (p.listen) {
|
|
3767
4232
|
const page2 = ctx.page;
|
|
3768
|
-
if (!page2) return
|
|
4233
|
+
if (!page2) return fail9("No active page. Use --cdp to connect first.");
|
|
3769
4234
|
const captures = [];
|
|
3770
4235
|
const consoleMessages = [];
|
|
3771
4236
|
const wsCaptures = [];
|
|
@@ -3792,7 +4257,7 @@ var networkCommand = registerCommand({
|
|
|
3792
4257
|
page2.off("response", handler);
|
|
3793
4258
|
const duration = Date.now() - startTime;
|
|
3794
4259
|
if (p.ws && wsCaptures.length > 0) {
|
|
3795
|
-
return
|
|
4260
|
+
return ok22({
|
|
3796
4261
|
url: "[listen mode]",
|
|
3797
4262
|
duration,
|
|
3798
4263
|
total: captures.length,
|
|
@@ -3806,9 +4271,9 @@ var networkCommand = registerCommand({
|
|
|
3806
4271
|
});
|
|
3807
4272
|
}
|
|
3808
4273
|
if (p.format === "json") {
|
|
3809
|
-
return
|
|
4274
|
+
return ok22(buildJsonOutput("[listen mode]", captures, consoleMessages, captures.length, wsCaptures));
|
|
3810
4275
|
}
|
|
3811
|
-
return
|
|
4276
|
+
return ok22(buildSummaryOutput("[listen mode]", duration, captures, consoleMessages, captures.length, wsCaptures));
|
|
3812
4277
|
}
|
|
3813
4278
|
const { context, page } = await createEphemeralContext(resolveLaunchOpts(ctx));
|
|
3814
4279
|
try {
|
|
@@ -3863,7 +4328,7 @@ var networkCommand = registerCommand({
|
|
|
3863
4328
|
}
|
|
3864
4329
|
const duration = Date.now() - startTime;
|
|
3865
4330
|
if (p.ws && wsCaptures.length > 0) {
|
|
3866
|
-
return
|
|
4331
|
+
return ok22({
|
|
3867
4332
|
url: p.url,
|
|
3868
4333
|
duration,
|
|
3869
4334
|
total: captures.length,
|
|
@@ -3877,9 +4342,9 @@ var networkCommand = registerCommand({
|
|
|
3877
4342
|
});
|
|
3878
4343
|
}
|
|
3879
4344
|
if (p.format === "json") {
|
|
3880
|
-
return
|
|
4345
|
+
return ok22(buildJsonOutput(p.url, results, consoleMessages, totalCount, wsCaptures, searchResults));
|
|
3881
4346
|
}
|
|
3882
|
-
return
|
|
4347
|
+
return ok22(buildSummaryOutput(p.url, duration, results, consoleMessages, totalCount, wsCaptures));
|
|
3883
4348
|
} finally {
|
|
3884
4349
|
await closeEphemeralContext(context);
|
|
3885
4350
|
}
|
|
@@ -3887,7 +4352,7 @@ var networkCommand = registerCommand({
|
|
|
3887
4352
|
});
|
|
3888
4353
|
|
|
3889
4354
|
// src/commands/ai-search-engines.ts
|
|
3890
|
-
import { z as
|
|
4355
|
+
import { z as z23 } from "zod";
|
|
3891
4356
|
var ENGINE_CONFIGS = {
|
|
3892
4357
|
deepseek: {
|
|
3893
4358
|
key: "deepseek",
|
|
@@ -4141,11 +4606,11 @@ var ENGINE_CONFIGS = {
|
|
|
4141
4606
|
}
|
|
4142
4607
|
};
|
|
4143
4608
|
var ALL_ENGINE_KEYS = Object.keys(ENGINE_CONFIGS);
|
|
4144
|
-
var ENGINE_KEY_ENUM =
|
|
4609
|
+
var ENGINE_KEY_ENUM = z23.enum(ALL_ENGINE_KEYS);
|
|
4145
4610
|
|
|
4146
4611
|
// src/commands/snapshot.ts
|
|
4147
|
-
import { z as
|
|
4148
|
-
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";
|
|
4149
4614
|
|
|
4150
4615
|
// src/runtime/ref-store.ts
|
|
4151
4616
|
var sessions = /* @__PURE__ */ new Map();
|
|
@@ -4386,8 +4851,8 @@ async function resolveRefParams(page, params, selectorKeys, cache, sessionId) {
|
|
|
4386
4851
|
}
|
|
4387
4852
|
|
|
4388
4853
|
// src/utils/site-semantics.ts
|
|
4389
|
-
import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync, readFileSync } from "fs";
|
|
4390
|
-
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";
|
|
4391
4856
|
import { homedir as homedir4 } from "os";
|
|
4392
4857
|
import { stringify, parse } from "yaml";
|
|
4393
4858
|
import { execFile } from "child_process";
|
|
@@ -4465,10 +4930,10 @@ function inferAction(role, label) {
|
|
|
4465
4930
|
return {};
|
|
4466
4931
|
}
|
|
4467
4932
|
function getSemanticsDir() {
|
|
4468
|
-
return
|
|
4933
|
+
return join6(homedir4(), ".xbrowser", "site-semantics");
|
|
4469
4934
|
}
|
|
4470
4935
|
function getSemanticsPath(domain) {
|
|
4471
|
-
return
|
|
4936
|
+
return join6(getSemanticsDir(), `${domain}.yaml`);
|
|
4472
4937
|
}
|
|
4473
4938
|
function extractDomain2(url) {
|
|
4474
4939
|
try {
|
|
@@ -4482,9 +4947,9 @@ function saveSemantics(domain, pagePath, url, elements) {
|
|
|
4482
4947
|
const filePath = getSemanticsPath(domain);
|
|
4483
4948
|
const dir = dirname2(filePath);
|
|
4484
4949
|
let site;
|
|
4485
|
-
if (
|
|
4950
|
+
if (existsSync3(filePath)) {
|
|
4486
4951
|
try {
|
|
4487
|
-
site = parse(
|
|
4952
|
+
site = parse(readFileSync3(filePath, "utf-8"));
|
|
4488
4953
|
} catch {
|
|
4489
4954
|
site = { site: domain, pages: {} };
|
|
4490
4955
|
}
|
|
@@ -4504,16 +4969,16 @@ function saveSemantics(domain, pagePath, url, elements) {
|
|
|
4504
4969
|
}
|
|
4505
4970
|
}
|
|
4506
4971
|
site.updated_at = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
4507
|
-
if (!
|
|
4972
|
+
if (!existsSync3(dir)) {
|
|
4508
4973
|
mkdirSync3(dir, { recursive: true });
|
|
4509
4974
|
}
|
|
4510
4975
|
writeFileSync3(filePath, stringify(site, { lineWidth: 0 }));
|
|
4511
4976
|
}
|
|
4512
4977
|
function loadSemantics(domain) {
|
|
4513
4978
|
const filePath = getSemanticsPath(domain);
|
|
4514
|
-
if (!
|
|
4979
|
+
if (!existsSync3(filePath)) return null;
|
|
4515
4980
|
try {
|
|
4516
|
-
return parse(
|
|
4981
|
+
return parse(readFileSync3(filePath, "utf-8"));
|
|
4517
4982
|
} catch {
|
|
4518
4983
|
return null;
|
|
4519
4984
|
}
|
|
@@ -5057,24 +5522,24 @@ var snapshotCommand = registerCommand({
|
|
|
5057
5522
|
description: "Capture a quick page state snapshot \u2014 aria tree, visible text, or DOM summary",
|
|
5058
5523
|
scope: "page",
|
|
5059
5524
|
selectorParams: ["selector"],
|
|
5060
|
-
parameters:
|
|
5061
|
-
type:
|
|
5062
|
-
selector:
|
|
5063
|
-
depth:
|
|
5064
|
-
interactive:
|
|
5065
|
-
interactiveOnly:
|
|
5066
|
-
i:
|
|
5067
|
-
compact:
|
|
5068
|
-
c:
|
|
5069
|
-
selectors:
|
|
5070
|
-
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")
|
|
5071
5536
|
}),
|
|
5072
|
-
result:
|
|
5073
|
-
url:
|
|
5074
|
-
title:
|
|
5075
|
-
aria:
|
|
5076
|
-
text:
|
|
5077
|
-
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()
|
|
5078
5543
|
}),
|
|
5079
5544
|
handler: async (p, ctx) => {
|
|
5080
5545
|
const page = ctx.page;
|
|
@@ -5088,7 +5553,7 @@ var snapshotCommand = registerCommand({
|
|
|
5088
5553
|
if (p.compact || p.c || p.interactive || p.interactiveOnly || p.i) {
|
|
5089
5554
|
observation.compact = formatObservationCompact(observation, { selectors: p.selectors });
|
|
5090
5555
|
}
|
|
5091
|
-
return
|
|
5556
|
+
return ok23(observation, normalizeTips3([
|
|
5092
5557
|
`refs refreshed for ${observation.targets.length} targets; use click @e1 or fill @e2 "text"`
|
|
5093
5558
|
]));
|
|
5094
5559
|
}
|
|
@@ -5096,15 +5561,15 @@ var snapshotCommand = registerCommand({
|
|
|
5096
5561
|
const aria = await captureAriaSnapshot(page, p.selector, p.depth);
|
|
5097
5562
|
const tips = await buildRefTips(page, aria);
|
|
5098
5563
|
persistSemantics(url, aria);
|
|
5099
|
-
return
|
|
5564
|
+
return ok23({ url, title, aria }, normalizeTips3(tips));
|
|
5100
5565
|
}
|
|
5101
5566
|
if (p.type === "text") {
|
|
5102
5567
|
const text = await captureTextSnapshot(page, p.selector);
|
|
5103
|
-
return
|
|
5568
|
+
return ok23({ url, title, text });
|
|
5104
5569
|
}
|
|
5105
5570
|
if (p.type === "dom") {
|
|
5106
5571
|
const dom = await captureDomSnapshot(page, p.selector, p.depth ?? 6);
|
|
5107
|
-
return
|
|
5572
|
+
return ok23({ url, title, dom });
|
|
5108
5573
|
}
|
|
5109
5574
|
if (p.type === "all") {
|
|
5110
5575
|
const [aria, text, dom] = await Promise.all([
|
|
@@ -5114,9 +5579,9 @@ var snapshotCommand = registerCommand({
|
|
|
5114
5579
|
]);
|
|
5115
5580
|
const tips = await buildRefTips(page, aria);
|
|
5116
5581
|
persistSemantics(url, aria);
|
|
5117
|
-
return
|
|
5582
|
+
return ok23({ url, title, aria, text, dom }, normalizeTips3(tips));
|
|
5118
5583
|
}
|
|
5119
|
-
return
|
|
5584
|
+
return fail10(`Unknown snapshot type: ${p.type}`);
|
|
5120
5585
|
}
|
|
5121
5586
|
});
|
|
5122
5587
|
function persistSemantics(url, aria) {
|
|
@@ -5193,22 +5658,22 @@ async function captureDomSnapshot(page, selector, maxDepth) {
|
|
|
5193
5658
|
}
|
|
5194
5659
|
|
|
5195
5660
|
// src/commands/agent.ts
|
|
5196
|
-
import { z as
|
|
5197
|
-
import { ok as
|
|
5661
|
+
import { z as z25 } from "zod";
|
|
5662
|
+
import { ok as ok24, normalizeTips as normalizeTips4 } from "@dyyz1993/xcli-core";
|
|
5198
5663
|
var observeCommand = registerCommand({
|
|
5199
5664
|
name: "observe",
|
|
5200
5665
|
description: "Observe the current page as structured agent targets with session refs",
|
|
5201
5666
|
scope: "page",
|
|
5202
|
-
parameters:
|
|
5203
|
-
includeHidden:
|
|
5204
|
-
limit:
|
|
5205
|
-
compact:
|
|
5206
|
-
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")
|
|
5207
5672
|
}),
|
|
5208
|
-
result:
|
|
5209
|
-
targets:
|
|
5210
|
-
selectors:
|
|
5211
|
-
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()
|
|
5212
5677
|
}).passthrough(),
|
|
5213
5678
|
handler: async (p, ctx) => {
|
|
5214
5679
|
const observation = await observePage(ctx.page, ctx.sessionId, {
|
|
@@ -5217,7 +5682,7 @@ var observeCommand = registerCommand({
|
|
|
5217
5682
|
});
|
|
5218
5683
|
if (p.selectors) observation.selectors = buildSelectorMap(observation);
|
|
5219
5684
|
if (p.compact) observation.compact = formatObservationCompact(observation, { selectors: p.selectors });
|
|
5220
|
-
return
|
|
5685
|
+
return ok24(observation, normalizeTips4([
|
|
5221
5686
|
`refs refreshed for ${observation.targets.length} targets; use act --ref @e1 --action click or click @e1`
|
|
5222
5687
|
]));
|
|
5223
5688
|
}
|
|
@@ -5227,14 +5692,14 @@ var actCommand = registerCommand({
|
|
|
5227
5692
|
description: "Perform an agent action using an observe ref or explicit selector",
|
|
5228
5693
|
scope: "element",
|
|
5229
5694
|
selectorParams: ["selector"],
|
|
5230
|
-
parameters:
|
|
5231
|
-
action:
|
|
5232
|
-
ref:
|
|
5233
|
-
selector:
|
|
5234
|
-
value:
|
|
5235
|
-
key:
|
|
5236
|
-
force:
|
|
5237
|
-
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")
|
|
5238
5703
|
}).refine((p) => !!p.ref || !!p.selector, {
|
|
5239
5704
|
message: "Either ref or selector is required"
|
|
5240
5705
|
}),
|
|
@@ -5248,7 +5713,7 @@ var actCommand = registerCommand({
|
|
|
5248
5713
|
tips: normalizeTips4(result.stale ? ["run observe again to refresh refs"] : [])
|
|
5249
5714
|
};
|
|
5250
5715
|
}
|
|
5251
|
-
return
|
|
5716
|
+
return ok24(result, normalizeTips4(result.stale ? ["ref screen hash changed; run observe if the next action is uncertain"] : []));
|
|
5252
5717
|
}
|
|
5253
5718
|
});
|
|
5254
5719
|
var waitForCommand = registerCommand({
|
|
@@ -5256,16 +5721,16 @@ var waitForCommand = registerCommand({
|
|
|
5256
5721
|
description: "Wait for agent predicates such as text, URL, load state, selector state, or screen hash changes",
|
|
5257
5722
|
scope: "page",
|
|
5258
5723
|
selectorParams: ["selector"],
|
|
5259
|
-
parameters:
|
|
5260
|
-
selector:
|
|
5261
|
-
state:
|
|
5262
|
-
text:
|
|
5263
|
-
url:
|
|
5264
|
-
load:
|
|
5265
|
-
fn:
|
|
5266
|
-
screenHashChanged:
|
|
5267
|
-
timeout:
|
|
5268
|
-
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)
|
|
5269
5734
|
}).refine((p) => [p.selector, p.text, p.url, p.load, p.fn, p.screenHashChanged].filter(Boolean).length === 1, {
|
|
5270
5735
|
message: "Provide exactly one wait predicate: selector, text, url, load, fn, or screenHashChanged"
|
|
5271
5736
|
}),
|
|
@@ -5279,30 +5744,30 @@ var waitForCommand = registerCommand({
|
|
|
5279
5744
|
tips: []
|
|
5280
5745
|
};
|
|
5281
5746
|
}
|
|
5282
|
-
return
|
|
5747
|
+
return ok24(result);
|
|
5283
5748
|
}
|
|
5284
5749
|
});
|
|
5285
5750
|
|
|
5286
5751
|
// src/commands/tab.ts
|
|
5287
|
-
import { z as
|
|
5288
|
-
import { ok as
|
|
5289
|
-
var TabParams =
|
|
5290
|
-
subcommand:
|
|
5291
|
-
url:
|
|
5292
|
-
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()
|
|
5293
5758
|
});
|
|
5294
5759
|
var tabCommand = registerCommand({
|
|
5295
5760
|
name: "tab",
|
|
5296
5761
|
description: "Manage browser tabs: list, new, close, switch",
|
|
5297
5762
|
scope: "page",
|
|
5298
5763
|
parameters: TabParams,
|
|
5299
|
-
result:
|
|
5300
|
-
success:
|
|
5301
|
-
data:
|
|
5764
|
+
result: z26.object({
|
|
5765
|
+
success: z26.boolean(),
|
|
5766
|
+
data: z26.unknown()
|
|
5302
5767
|
}),
|
|
5303
5768
|
handler: async (p, ctx) => {
|
|
5304
5769
|
if (!ctx.browserContext) {
|
|
5305
|
-
return
|
|
5770
|
+
return fail11("No browser context available. Use --cdp to connect to a browser first.");
|
|
5306
5771
|
}
|
|
5307
5772
|
const pages = ctx.browserContext.pages();
|
|
5308
5773
|
switch (p.subcommand) {
|
|
@@ -5315,7 +5780,7 @@ var tabCommand = registerCommand({
|
|
|
5315
5780
|
case "switch":
|
|
5316
5781
|
return handleSwitch(p, pages, ctx);
|
|
5317
5782
|
default:
|
|
5318
|
-
return
|
|
5783
|
+
return fail11(`Unknown subcommand: ${p.subcommand}`);
|
|
5319
5784
|
}
|
|
5320
5785
|
}
|
|
5321
5786
|
});
|
|
@@ -5330,7 +5795,7 @@ async function handleList(pages, ctx) {
|
|
|
5330
5795
|
if (isActive) activeIndex = i;
|
|
5331
5796
|
tabs.push({ index: i, url, title, active: isActive });
|
|
5332
5797
|
}
|
|
5333
|
-
return
|
|
5798
|
+
return ok25({ tabs, total: tabs.length, activeIndex });
|
|
5334
5799
|
}
|
|
5335
5800
|
async function handleNew(p, _pages, ctx) {
|
|
5336
5801
|
const newPage = await ctx.browserContext.newPage();
|
|
@@ -5358,7 +5823,7 @@ async function handleNew(p, _pages, ctx) {
|
|
|
5358
5823
|
const title = await newPage.title().catch(() => "");
|
|
5359
5824
|
const allPages = ctx.browserContext.pages();
|
|
5360
5825
|
const newIndex = allPages.indexOf(newPage);
|
|
5361
|
-
return
|
|
5826
|
+
return ok25({
|
|
5362
5827
|
index: newIndex >= 0 ? newIndex : allPages.length - 1,
|
|
5363
5828
|
url: newPage.url(),
|
|
5364
5829
|
title,
|
|
@@ -5369,11 +5834,11 @@ async function handleNew(p, _pages, ctx) {
|
|
|
5369
5834
|
async function handleClose(p, ctx) {
|
|
5370
5835
|
const currentPages = ctx.browserContext.pages();
|
|
5371
5836
|
if (currentPages.length <= 1) {
|
|
5372
|
-
return
|
|
5837
|
+
return fail11("Cannot close the last remaining tab");
|
|
5373
5838
|
}
|
|
5374
5839
|
const closeIndex = p.index ?? currentPages.findIndex((pg) => pg === ctx.page);
|
|
5375
5840
|
if (closeIndex < 0 || closeIndex >= currentPages.length) {
|
|
5376
|
-
return
|
|
5841
|
+
return fail11(`Invalid tab index: ${closeIndex}. Valid range: 0-${currentPages.length - 1}`);
|
|
5377
5842
|
}
|
|
5378
5843
|
const pageToClose = currentPages[closeIndex];
|
|
5379
5844
|
const isActivePage = pageToClose === ctx.page;
|
|
@@ -5388,7 +5853,7 @@ async function handleClose(p, ctx) {
|
|
|
5388
5853
|
}
|
|
5389
5854
|
ctx.page = newActivePage;
|
|
5390
5855
|
}
|
|
5391
|
-
return
|
|
5856
|
+
return ok25({
|
|
5392
5857
|
closedIndex: closeIndex,
|
|
5393
5858
|
total: remainingPages.length,
|
|
5394
5859
|
activeIndex: isActivePage ? closeIndex < remainingPages.length ? closeIndex : remainingPages.length - 1 : remainingPages.findIndex((pg) => pg === ctx.page)
|
|
@@ -5396,21 +5861,32 @@ async function handleClose(p, ctx) {
|
|
|
5396
5861
|
}
|
|
5397
5862
|
async function handleSwitch(p, pages, ctx) {
|
|
5398
5863
|
if (p.index === void 0) {
|
|
5399
|
-
return
|
|
5864
|
+
return fail11("Parameter --index is required for switch subcommand");
|
|
5400
5865
|
}
|
|
5401
5866
|
if (p.index < 0 || p.index >= pages.length) {
|
|
5402
|
-
return
|
|
5867
|
+
return fail11(`Invalid tab index: ${p.index}. Valid range: 0-${pages.length - 1}`);
|
|
5403
5868
|
}
|
|
5404
5869
|
const targetPage = pages[p.index];
|
|
5405
5870
|
await targetPage.bringToFront().catch(() => {
|
|
5406
5871
|
});
|
|
5872
|
+
for (let i = 0; i < 5; i++) {
|
|
5873
|
+
const okProbe = await targetPage.evaluate("1").then(() => true, () => false);
|
|
5874
|
+
if (okProbe) break;
|
|
5875
|
+
await new Promise((r) => setTimeout(r, 300));
|
|
5876
|
+
}
|
|
5877
|
+
try {
|
|
5878
|
+
const mouse = targetPage.mouse;
|
|
5879
|
+
if (mouse) await mouse.move(1, 1).catch(() => {
|
|
5880
|
+
});
|
|
5881
|
+
} catch {
|
|
5882
|
+
}
|
|
5407
5883
|
const session = ctx.sessionId ? getSessionById(ctx.sessionId) : void 0;
|
|
5408
5884
|
if (session) {
|
|
5409
5885
|
setActivePage(session, targetPage);
|
|
5410
5886
|
}
|
|
5411
5887
|
ctx.page = targetPage;
|
|
5412
5888
|
const title = await targetPage.title().catch(() => "");
|
|
5413
|
-
return
|
|
5889
|
+
return ok25({
|
|
5414
5890
|
index: p.index,
|
|
5415
5891
|
url: targetPage.url(),
|
|
5416
5892
|
title,
|
|
@@ -5419,9 +5895,9 @@ async function handleSwitch(p, pages, ctx) {
|
|
|
5419
5895
|
}
|
|
5420
5896
|
|
|
5421
5897
|
// src/commands/addinitscript.ts
|
|
5422
|
-
import { z as
|
|
5423
|
-
import { ok as
|
|
5424
|
-
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";
|
|
5425
5901
|
|
|
5426
5902
|
// src/chain-parser.ts
|
|
5427
5903
|
import { splitCommand, parseCommandArgs } from "@dyyz1993/xcli-core";
|
|
@@ -5561,14 +6037,14 @@ registerCommandDefinition("tab", ["subcommand"]);
|
|
|
5561
6037
|
registerCommandDefinition("mouse", ["action", "x", "y"]);
|
|
5562
6038
|
|
|
5563
6039
|
// src/commands/addinitscript.ts
|
|
5564
|
-
var InitScriptParams =
|
|
5565
|
-
script:
|
|
5566
|
-
file:
|
|
5567
|
-
stdin:
|
|
5568
|
-
name:
|
|
5569
|
-
list:
|
|
5570
|
-
remove:
|
|
5571
|
-
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()
|
|
5572
6048
|
});
|
|
5573
6049
|
var registeredScripts = /* @__PURE__ */ new Map();
|
|
5574
6050
|
function resolveScriptContent(params) {
|
|
@@ -5576,7 +6052,7 @@ function resolveScriptContent(params) {
|
|
|
5576
6052
|
return Buffer.from(params.base64, "base64").toString("utf-8");
|
|
5577
6053
|
}
|
|
5578
6054
|
if (params.file) {
|
|
5579
|
-
return
|
|
6055
|
+
return readFileSync4(params.file, "utf-8");
|
|
5580
6056
|
}
|
|
5581
6057
|
if (params.stdin) {
|
|
5582
6058
|
return null;
|
|
@@ -5602,14 +6078,14 @@ var addInitScriptCommand = registerCommand({
|
|
|
5602
6078
|
description: "Add an initialization script that runs on every page load",
|
|
5603
6079
|
scope: "page",
|
|
5604
6080
|
parameters: InitScriptParams,
|
|
5605
|
-
result:
|
|
5606
|
-
scripts:
|
|
5607
|
-
removed:
|
|
5608
|
-
existed:
|
|
5609
|
-
error:
|
|
5610
|
-
registered:
|
|
5611
|
-
hint:
|
|
5612
|
-
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()
|
|
5613
6089
|
}).passthrough(),
|
|
5614
6090
|
handler: async (params, ctx) => {
|
|
5615
6091
|
if (params.list) {
|
|
@@ -5618,20 +6094,20 @@ var addInitScriptCommand = registerCommand({
|
|
|
5618
6094
|
size: content2.length,
|
|
5619
6095
|
preview: content2.slice(0, 80)
|
|
5620
6096
|
}));
|
|
5621
|
-
return
|
|
6097
|
+
return ok26({ scripts });
|
|
5622
6098
|
}
|
|
5623
6099
|
if (params.remove && typeof params.remove === "string") {
|
|
5624
6100
|
const existed = registeredScripts.delete(params.remove);
|
|
5625
|
-
return
|
|
6101
|
+
return ok26({ removed: params.remove, existed });
|
|
5626
6102
|
}
|
|
5627
6103
|
const removeTarget = (typeof params.remove === "string" ? params.remove : null) || (params.name && !resolveScriptContent(params) ? params.name : null);
|
|
5628
6104
|
if (removeTarget && !resolveScriptContent(params)) {
|
|
5629
6105
|
const existed = registeredScripts.delete(removeTarget);
|
|
5630
|
-
return
|
|
6106
|
+
return ok26({ removed: removeTarget, existed });
|
|
5631
6107
|
}
|
|
5632
6108
|
let content = params.stdin ? await readStdin() : resolveScriptContent(params);
|
|
5633
6109
|
if (!content) {
|
|
5634
|
-
return
|
|
6110
|
+
return ok26({ error: "No script content provided. Use --script, --file, --stdin, or --base64" });
|
|
5635
6111
|
}
|
|
5636
6112
|
const scriptName = params.name ?? `script-${Date.now()}`;
|
|
5637
6113
|
registeredScripts.set(scriptName, content);
|
|
@@ -5639,45 +6115,45 @@ var addInitScriptCommand = registerCommand({
|
|
|
5639
6115
|
try {
|
|
5640
6116
|
await ctx.page.evaluate(content);
|
|
5641
6117
|
} catch {
|
|
5642
|
-
return
|
|
6118
|
+
return ok26({
|
|
5643
6119
|
registered: scriptName,
|
|
5644
6120
|
hint: "Script registered for future page loads; immediate execution skipped (page may not be ready)"
|
|
5645
6121
|
});
|
|
5646
6122
|
}
|
|
5647
|
-
return
|
|
6123
|
+
return ok26({ registered: scriptName, executedImmediately: true });
|
|
5648
6124
|
}
|
|
5649
6125
|
});
|
|
5650
6126
|
registerCommandDefinition("addinitscript", ["script"]);
|
|
5651
6127
|
|
|
5652
6128
|
// src/commands/find.ts
|
|
5653
|
-
import { z as
|
|
5654
|
-
import { ok as
|
|
5655
|
-
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"]);
|
|
5656
6132
|
var findCommand = registerCommand({
|
|
5657
6133
|
name: "find",
|
|
5658
6134
|
description: "Find elements by semantic strategy (text/role/label/placeholder/testid) and optionally perform an action",
|
|
5659
6135
|
scope: "page",
|
|
5660
|
-
parameters:
|
|
5661
|
-
strategy:
|
|
5662
|
-
value:
|
|
5663
|
-
name:
|
|
5664
|
-
exact:
|
|
5665
|
-
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"'),
|
|
5666
6142
|
action: actionSchema2.optional().describe("Action to perform when not using trailing operation syntax"),
|
|
5667
|
-
actionValue:
|
|
5668
|
-
index:
|
|
5669
|
-
click:
|
|
5670
|
-
fill:
|
|
5671
|
-
type:
|
|
5672
|
-
select:
|
|
5673
|
-
hover:
|
|
5674
|
-
check:
|
|
5675
|
-
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)
|
|
5676
6152
|
}),
|
|
5677
|
-
result:
|
|
5678
|
-
matched:
|
|
5679
|
-
selector:
|
|
5680
|
-
action:
|
|
6153
|
+
result: z28.object({
|
|
6154
|
+
matched: z28.number(),
|
|
6155
|
+
selector: z28.string(),
|
|
6156
|
+
action: z28.string().optional()
|
|
5681
6157
|
}),
|
|
5682
6158
|
handler: async (p, ctx) => {
|
|
5683
6159
|
const page = ctx.page;
|
|
@@ -5692,7 +6168,7 @@ var findCommand = registerCommand({
|
|
|
5692
6168
|
});
|
|
5693
6169
|
const count = await locator.count();
|
|
5694
6170
|
if (count === 0) {
|
|
5695
|
-
return
|
|
6171
|
+
return fail12(`No element found with ${p.strategy}="${p.value}"`);
|
|
5696
6172
|
}
|
|
5697
6173
|
const tips = [];
|
|
5698
6174
|
const target = selectTarget(locator, p.strategy);
|
|
@@ -5704,15 +6180,15 @@ var findCommand = registerCommand({
|
|
|
5704
6180
|
await target.click({ timeout: p.timeout, force: true });
|
|
5705
6181
|
return okWithTips({ matched: count, selector, action: "click" }, tips);
|
|
5706
6182
|
} else if (actionName === "fill") {
|
|
5707
|
-
if (actionValue === void 0) return
|
|
6183
|
+
if (actionValue === void 0) return fail12("find fill requires a value");
|
|
5708
6184
|
await target.fill(actionValue, { timeout: p.timeout, force: true });
|
|
5709
6185
|
return okWithTips({ matched: count, selector, action: `fill("${actionValue}")` }, tips);
|
|
5710
6186
|
} else if (actionName === "type") {
|
|
5711
|
-
if (actionValue === void 0) return
|
|
6187
|
+
if (actionValue === void 0) return fail12("find type requires a value");
|
|
5712
6188
|
await target.type(actionValue, { delay: 10, timeout: p.timeout });
|
|
5713
6189
|
return okWithTips({ matched: count, selector, action: `type("${actionValue}")` }, tips);
|
|
5714
6190
|
} else if (actionName === "select") {
|
|
5715
|
-
if (actionValue === void 0) return
|
|
6191
|
+
if (actionValue === void 0) return fail12("find select requires a value");
|
|
5716
6192
|
await target.selectOption(actionValue);
|
|
5717
6193
|
return okWithTips({ matched: count, selector, action: `select("${actionValue}")` }, tips);
|
|
5718
6194
|
} else if (actionName === "hover") {
|
|
@@ -5726,7 +6202,7 @@ var findCommand = registerCommand({
|
|
|
5726
6202
|
}
|
|
5727
6203
|
});
|
|
5728
6204
|
function okWithTips(data, tips) {
|
|
5729
|
-
const result =
|
|
6205
|
+
const result = ok27(data);
|
|
5730
6206
|
if (tips.length > 0) result.tips = normalizeTips5(tips);
|
|
5731
6207
|
return result;
|
|
5732
6208
|
}
|
|
@@ -5827,7 +6303,7 @@ function describeSelector(strategy, value, name) {
|
|
|
5827
6303
|
// src/context.ts
|
|
5828
6304
|
function attachDetectAntiBot(ctx) {
|
|
5829
6305
|
ctx.detectAntiBot = async (page, config) => {
|
|
5830
|
-
const { detectAntiBot: detectAntiBot2 } = await import("./anti-bot-
|
|
6306
|
+
const { detectAntiBot: detectAntiBot2 } = await import("./anti-bot-GTTYNEFB.js");
|
|
5831
6307
|
return detectAntiBot2(page, config);
|
|
5832
6308
|
};
|
|
5833
6309
|
}
|
|
@@ -6437,7 +6913,7 @@ async function loadHooks() {
|
|
|
6437
6913
|
|
|
6438
6914
|
// src/executor.ts
|
|
6439
6915
|
import { homedir as homedir5 } from "os";
|
|
6440
|
-
import { join as
|
|
6916
|
+
import { join as join7 } from "path";
|
|
6441
6917
|
function levenshtein(a, b) {
|
|
6442
6918
|
const m = a.length, n = b.length;
|
|
6443
6919
|
const dp = Array(m + 1).fill(null).map(() => Array(n + 1).fill(0));
|
|
@@ -6452,7 +6928,7 @@ function levenshtein(a, b) {
|
|
|
6452
6928
|
}
|
|
6453
6929
|
var NAVIGATION_COMMANDS = /* @__PURE__ */ new Set(["goto", "back", "forward", "refresh"]);
|
|
6454
6930
|
var snapshotHintShown = /* @__PURE__ */ new WeakSet();
|
|
6455
|
-
var CONFIG_DIR2 =
|
|
6931
|
+
var CONFIG_DIR2 = join7(homedir5(), ".xbrowser");
|
|
6456
6932
|
var storageCache = /* @__PURE__ */ new Map();
|
|
6457
6933
|
function getPluginStorage(pluginName) {
|
|
6458
6934
|
if (!storageCache.has(pluginName)) {
|
|
@@ -6464,7 +6940,7 @@ var archiveInitialized = false;
|
|
|
6464
6940
|
function ensureArchiveInit() {
|
|
6465
6941
|
if (!archiveInitialized) {
|
|
6466
6942
|
try {
|
|
6467
|
-
configureArchiveStore({ archiveDir:
|
|
6943
|
+
configureArchiveStore({ archiveDir: join7(homedir5(), ".xbrowser", "archives") });
|
|
6468
6944
|
} catch {
|
|
6469
6945
|
}
|
|
6470
6946
|
archiveInitialized = true;
|
|
@@ -6488,7 +6964,7 @@ async function guardCheck(commandName) {
|
|
|
6488
6964
|
}
|
|
6489
6965
|
}
|
|
6490
6966
|
function errorResult(message) {
|
|
6491
|
-
return { ...
|
|
6967
|
+
return { ...fail13(message), duration: 0 };
|
|
6492
6968
|
}
|
|
6493
6969
|
function tipsToMessages(tips) {
|
|
6494
6970
|
if (!tips || tips.length === 0) return [];
|
|
@@ -6565,7 +7041,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
6565
7041
|
}
|
|
6566
7042
|
let targetPageOverride = null;
|
|
6567
7043
|
if (_target && extraOpts?.cdpEndpoint) {
|
|
6568
|
-
const { findTargetPage } = await import("./browser-
|
|
7044
|
+
const { findTargetPage } = await import("./browser-XPDMY2NX.js");
|
|
6569
7045
|
targetPageOverride = await findTargetPage(extraOpts.cdpEndpoint, _target);
|
|
6570
7046
|
if (!targetPageOverride) {
|
|
6571
7047
|
return errorResult(`Target "${_target}" not found. Use 'xbrowser targets --cdp ${extraOpts.cdpEndpoint}' to list available pages.`);
|
|
@@ -6582,8 +7058,15 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
6582
7058
|
params = result.data;
|
|
6583
7059
|
}
|
|
6584
7060
|
if (command.scope !== "cli" && !process.env.XBROWSER_DAEMON_WORKER) {
|
|
6585
|
-
const { forwardExec } = await import("./daemon-client-
|
|
6586
|
-
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
|
+
);
|
|
6587
7070
|
if (result) return result;
|
|
6588
7071
|
}
|
|
6589
7072
|
let session;
|
|
@@ -6751,7 +7234,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
6751
7234
|
timestamp: start
|
|
6752
7235
|
});
|
|
6753
7236
|
if (isSuccess) {
|
|
6754
|
-
return { ...
|
|
7237
|
+
return { ...ok28(raw.data, merged.length > 0 ? merged : raw.tips), duration, ...hookOutputs ? { hookOutputs } : {} };
|
|
6755
7238
|
}
|
|
6756
7239
|
return { success: false, data: raw.data, message: raw.message, tips: mergedOrRaw, duration, ...hookOutputs ? { hookOutputs } : {} };
|
|
6757
7240
|
}
|
|
@@ -6765,14 +7248,14 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
6765
7248
|
duration,
|
|
6766
7249
|
timestamp: start
|
|
6767
7250
|
});
|
|
6768
|
-
return { ...
|
|
7251
|
+
return { ...ok28(raw, smartTipNormalized), duration, ...hookOutputs ? { hookOutputs } : {} };
|
|
6769
7252
|
} catch (err) {
|
|
6770
7253
|
const end = Date.now();
|
|
6771
7254
|
const duration = end - start;
|
|
6772
7255
|
const errorMessage = errMsg(err);
|
|
6773
7256
|
if (session?.page && process.env.XBROWSER_RECOVERY && !extraOpts?._recoveryAttempted) {
|
|
6774
7257
|
try {
|
|
6775
|
-
const { attemptRecovery } = await import("./recovery-
|
|
7258
|
+
const { attemptRecovery } = await import("./recovery-Z3RDZENA.js");
|
|
6776
7259
|
const recovery = await attemptRecovery(
|
|
6777
7260
|
session.page,
|
|
6778
7261
|
sessionName,
|
|
@@ -6810,7 +7293,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
6810
7293
|
duration,
|
|
6811
7294
|
timestamp: start
|
|
6812
7295
|
});
|
|
6813
|
-
return { ...
|
|
7296
|
+
return { ...fail13(errorMessage), duration };
|
|
6814
7297
|
} finally {
|
|
6815
7298
|
}
|
|
6816
7299
|
}
|
|
@@ -6851,7 +7334,7 @@ async function executeChain(input, options) {
|
|
|
6851
7334
|
results.push({
|
|
6852
7335
|
command: cmdName,
|
|
6853
7336
|
raw: cmdStr,
|
|
6854
|
-
...
|
|
7337
|
+
...fail13(`Plugin "${cmdName}" requires a sub-command`),
|
|
6855
7338
|
duration: 0
|
|
6856
7339
|
});
|
|
6857
7340
|
if (type === "and") {
|
|
@@ -6870,7 +7353,7 @@ async function executeChain(input, options) {
|
|
|
6870
7353
|
results.push({
|
|
6871
7354
|
command: cmdName,
|
|
6872
7355
|
raw: cmdStr,
|
|
6873
|
-
...
|
|
7356
|
+
...fail13(`Unknown command "${subCommand}" for plugin "${cmdName}"`),
|
|
6874
7357
|
duration: 0
|
|
6875
7358
|
});
|
|
6876
7359
|
if (type === "and") {
|
|
@@ -6974,7 +7457,7 @@ async function executeChain(input, options) {
|
|
|
6974
7457
|
results.push({
|
|
6975
7458
|
command: `${cmdName} ${subCommand}`,
|
|
6976
7459
|
raw: cmdStr,
|
|
6977
|
-
...
|
|
7460
|
+
...ok28(data),
|
|
6978
7461
|
duration: duration2,
|
|
6979
7462
|
...hookOutputs ? { hookOutputs } : {}
|
|
6980
7463
|
});
|
|
@@ -7002,7 +7485,7 @@ async function executeChain(input, options) {
|
|
|
7002
7485
|
results.push({
|
|
7003
7486
|
command: `${cmdName} ${subCommand}`,
|
|
7004
7487
|
raw: cmdStr,
|
|
7005
|
-
...
|
|
7488
|
+
...fail13(errorMessage),
|
|
7006
7489
|
duration: duration2
|
|
7007
7490
|
});
|
|
7008
7491
|
if (type === "and") {
|
|
@@ -7306,10 +7789,10 @@ async function replayEntry(entry, options = {}) {
|
|
|
7306
7789
|
}
|
|
7307
7790
|
|
|
7308
7791
|
// src/daemon/feedback-store.ts
|
|
7309
|
-
import { readFileSync as
|
|
7310
|
-
import { join as
|
|
7792
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4 } from "fs";
|
|
7793
|
+
import { join as join8 } from "path";
|
|
7311
7794
|
import { homedir as homedir6 } from "os";
|
|
7312
|
-
var FEEDBACK_FILE =
|
|
7795
|
+
var FEEDBACK_FILE = join8(homedir6(), ".xbrowser", "feedback.json");
|
|
7313
7796
|
var FeedbackStore = class {
|
|
7314
7797
|
entries = [];
|
|
7315
7798
|
constructor() {
|
|
@@ -7317,7 +7800,7 @@ var FeedbackStore = class {
|
|
|
7317
7800
|
}
|
|
7318
7801
|
load() {
|
|
7319
7802
|
try {
|
|
7320
|
-
const data =
|
|
7803
|
+
const data = readFileSync5(FEEDBACK_FILE, "utf8");
|
|
7321
7804
|
this.entries = JSON.parse(data);
|
|
7322
7805
|
} catch {
|
|
7323
7806
|
this.entries = [];
|
|
@@ -7325,7 +7808,7 @@ var FeedbackStore = class {
|
|
|
7325
7808
|
}
|
|
7326
7809
|
save() {
|
|
7327
7810
|
try {
|
|
7328
|
-
mkdirSync4(
|
|
7811
|
+
mkdirSync4(join8(homedir6(), ".xbrowser"), { recursive: true });
|
|
7329
7812
|
writeFileSync4(FEEDBACK_FILE, JSON.stringify(this.entries, null, 2));
|
|
7330
7813
|
} catch {
|
|
7331
7814
|
}
|
|
@@ -7809,7 +8292,7 @@ function createRPCHandler() {
|
|
|
7809
8292
|
return result;
|
|
7810
8293
|
} catch (err) {
|
|
7811
8294
|
const errorMessage = errMsg(err);
|
|
7812
|
-
const { attemptRecovery } = await import("./recovery-
|
|
8295
|
+
const { attemptRecovery } = await import("./recovery-Z3RDZENA.js");
|
|
7813
8296
|
const recovery = await attemptRecovery(
|
|
7814
8297
|
session?.page,
|
|
7815
8298
|
sessionName,
|
|
@@ -7941,15 +8424,26 @@ function createRPCHandler() {
|
|
|
7941
8424
|
const needsPause = INTERACTION_COMMANDS2.has(command) && !!previewWS;
|
|
7942
8425
|
if (needsPause) await previewWS.pauseScreencast(sessionName);
|
|
7943
8426
|
let urlBeforeCommand;
|
|
8427
|
+
let preElement;
|
|
7944
8428
|
try {
|
|
7945
8429
|
const session = findSession(sessionName);
|
|
7946
8430
|
urlBeforeCommand = session?.page?.url();
|
|
8431
|
+
const sel = cmdParams.selector || cmdParams.css;
|
|
8432
|
+
if (session?.page && sel && INTERACTION_COMMANDS2.has(command)) {
|
|
8433
|
+
preElement = await session.page.evaluate(`
|
|
8434
|
+
(function() {
|
|
8435
|
+
const el = ${queryJS(sel)};
|
|
8436
|
+
if (!el || typeof window.__xb_describe !== 'function') return null;
|
|
8437
|
+
return window.__xb_describe(el);
|
|
8438
|
+
})()
|
|
8439
|
+
`);
|
|
8440
|
+
}
|
|
7947
8441
|
} catch {
|
|
7948
8442
|
}
|
|
7949
8443
|
try {
|
|
7950
8444
|
const result = await executeCommand(command, cmdParams, sessionName, { cdpEndpoint: endpoint });
|
|
7951
8445
|
registerSessionIfNew(sessionName);
|
|
7952
|
-
await injectCommandToRecorder(sessionName, command, cmdParams, urlBeforeCommand);
|
|
8446
|
+
await injectCommandToRecorder(sessionName, command, cmdParams, urlBeforeCommand, preElement);
|
|
7953
8447
|
return result;
|
|
7954
8448
|
} finally {
|
|
7955
8449
|
if (needsPause) await previewWS.resumeScreencast(sessionName).catch(() => {
|
|
@@ -8104,7 +8598,7 @@ function createRPCHandler() {
|
|
|
8104
8598
|
const limit = params.limit || 50;
|
|
8105
8599
|
return { session: sessionName, commands: commandLogStore.list(sessionName, { limit }) };
|
|
8106
8600
|
}
|
|
8107
|
-
async function injectCommandToRecorder(sessionName, command, params, urlBeforeCommand) {
|
|
8601
|
+
async function injectCommandToRecorder(sessionName, command, params, urlBeforeCommand, preElement) {
|
|
8108
8602
|
const recorder = activeRecorders.get(sessionName);
|
|
8109
8603
|
if (!recorder) return;
|
|
8110
8604
|
const COMMAND_ACTION_MAP = {
|
|
@@ -8112,33 +8606,48 @@ function createRPCHandler() {
|
|
|
8112
8606
|
fill: "cdp-fill",
|
|
8113
8607
|
click: "cdp-click",
|
|
8114
8608
|
type: "input",
|
|
8115
|
-
select: "change"
|
|
8609
|
+
select: "change",
|
|
8610
|
+
scroll: "scroll"
|
|
8116
8611
|
};
|
|
8117
8612
|
const actionType = COMMAND_ACTION_MAP[command];
|
|
8118
8613
|
if (!actionType) return;
|
|
8119
8614
|
const selector = params.selector || params.css;
|
|
8120
|
-
|
|
8615
|
+
let value = params.value || params.expression;
|
|
8616
|
+
if (value === void 0 || value === "") {
|
|
8617
|
+
value = params.text;
|
|
8618
|
+
}
|
|
8619
|
+
if (command === "scroll") {
|
|
8620
|
+
const dir = params.direction || "down";
|
|
8621
|
+
const dist = params.distance ?? 300;
|
|
8622
|
+
value = `${dir}:${dist}`;
|
|
8623
|
+
}
|
|
8121
8624
|
let currentUrl = params.url || urlBeforeCommand;
|
|
8122
8625
|
if (command !== "goto" && urlBeforeCommand && urlBeforeCommand !== "about:blank") {
|
|
8123
8626
|
currentUrl = urlBeforeCommand;
|
|
8124
8627
|
}
|
|
8125
|
-
let element;
|
|
8126
|
-
if (selector) {
|
|
8628
|
+
let element = preElement;
|
|
8629
|
+
if (!element && selector) {
|
|
8127
8630
|
try {
|
|
8128
8631
|
const session = findSession(sessionName);
|
|
8129
8632
|
if (session?.page) {
|
|
8130
|
-
element = await session.page.evaluate(
|
|
8131
|
-
|
|
8132
|
-
|
|
8133
|
-
|
|
8134
|
-
|
|
8633
|
+
element = await session.page.evaluate(`
|
|
8634
|
+
(function() {
|
|
8635
|
+
const el = ${queryJS(selector)};
|
|
8636
|
+
if (!el || typeof window.__xb_describe !== 'function') return null;
|
|
8637
|
+
return window.__xb_describe(el);
|
|
8638
|
+
})()
|
|
8639
|
+
`);
|
|
8135
8640
|
}
|
|
8136
8641
|
} catch {
|
|
8137
8642
|
}
|
|
8138
8643
|
}
|
|
8139
8644
|
await recorder.recordCommandAction({
|
|
8140
8645
|
type: actionType,
|
|
8141
|
-
selector
|
|
8646
|
+
// Prefer the described unique selector so the dedup key matches the
|
|
8647
|
+
// real action signal's key (which uses element.selector). The raw
|
|
8648
|
+
// command selector (e.g. "button") never equals the generated unique
|
|
8649
|
+
// selector, so dedup silently failed before (rec-duel d02/d03).
|
|
8650
|
+
selector: element?.selector || selector,
|
|
8142
8651
|
value,
|
|
8143
8652
|
url: currentUrl,
|
|
8144
8653
|
element
|
|
@@ -8323,7 +8832,7 @@ function createRPCHandler() {
|
|
|
8323
8832
|
let rawContent;
|
|
8324
8833
|
let parsed;
|
|
8325
8834
|
try {
|
|
8326
|
-
rawContent =
|
|
8835
|
+
rawContent = readFileSync7(file, "utf8");
|
|
8327
8836
|
try {
|
|
8328
8837
|
parsed = JSON.parse(rawContent);
|
|
8329
8838
|
} catch {
|
|
@@ -8348,7 +8857,7 @@ function createRPCHandler() {
|
|
|
8348
8857
|
if (isNewFormat) {
|
|
8349
8858
|
try {
|
|
8350
8859
|
const replayErrors = [];
|
|
8351
|
-
const { SessionReplayer } = await import("./session-replayer-
|
|
8860
|
+
const { SessionReplayer } = await import("./session-replayer-K4A6OI4M.js");
|
|
8352
8861
|
const replayer = new SessionReplayer({
|
|
8353
8862
|
page: session.page,
|
|
8354
8863
|
stepDelay: slowMo * 500,
|
|
@@ -8398,10 +8907,10 @@ function createRPCHandler() {
|
|
|
8398
8907
|
const errors = Array.isArray(input.errors) ? input.errors : [];
|
|
8399
8908
|
const eventsPlayed = typeof input.eventsPlayed === "number" ? input.eventsPlayed : 0;
|
|
8400
8909
|
const totalEvents = typeof input.totalEvents === "number" ? input.totalEvents : eventsPlayed + errors.length;
|
|
8401
|
-
const
|
|
8910
|
+
const ok29 = input.ok ?? errors.length === 0;
|
|
8402
8911
|
return {
|
|
8403
|
-
ok:
|
|
8404
|
-
success:
|
|
8912
|
+
ok: ok29,
|
|
8913
|
+
success: ok29,
|
|
8405
8914
|
duration: typeof input.duration === "number" ? input.duration : 0,
|
|
8406
8915
|
eventsPlayed,
|
|
8407
8916
|
totalEvents,
|
|
@@ -9400,12 +9909,12 @@ var FileListHandler = class {
|
|
|
9400
9909
|
const msg = ctx.message;
|
|
9401
9910
|
try {
|
|
9402
9911
|
const { readdirSync, statSync } = await import("fs");
|
|
9403
|
-
const { join:
|
|
9912
|
+
const { join: join10, resolve } = await import("path");
|
|
9404
9913
|
const targetPath = resolve(msg.path);
|
|
9405
9914
|
const entries = readdirSync(targetPath);
|
|
9406
9915
|
const files = entries.map((name) => {
|
|
9407
9916
|
try {
|
|
9408
|
-
const stat = statSync(
|
|
9917
|
+
const stat = statSync(join10(targetPath, name));
|
|
9409
9918
|
return { name, isDir: stat.isDirectory(), size: stat.size, modified: stat.mtime.toISOString() };
|
|
9410
9919
|
} catch {
|
|
9411
9920
|
return { name, isDir: false, size: 0, modified: "" };
|
|
@@ -9422,10 +9931,10 @@ var FileDownloadHandler = class {
|
|
|
9422
9931
|
async handle(ctx) {
|
|
9423
9932
|
const msg = ctx.message;
|
|
9424
9933
|
try {
|
|
9425
|
-
const { readFileSync:
|
|
9934
|
+
const { readFileSync: readFileSync8 } = await import("fs");
|
|
9426
9935
|
const { resolve, basename } = await import("path");
|
|
9427
9936
|
const targetPath = resolve(msg.path);
|
|
9428
|
-
const data =
|
|
9937
|
+
const data = readFileSync8(targetPath);
|
|
9429
9938
|
const base64 = data.toString("base64");
|
|
9430
9939
|
const ext = targetPath.split(".").pop()?.toLowerCase() || "";
|
|
9431
9940
|
const mimeMap = {
|
|
@@ -11415,8 +11924,8 @@ connectWS();
|
|
|
11415
11924
|
}
|
|
11416
11925
|
|
|
11417
11926
|
// src/daemon/daemon-main.ts
|
|
11418
|
-
var CONFIG_DIR3 =
|
|
11419
|
-
var LOG_FILE =
|
|
11927
|
+
var CONFIG_DIR3 = join9(homedir7(), ".xbrowser");
|
|
11928
|
+
var LOG_FILE = join9(CONFIG_DIR3, "daemon.log");
|
|
11420
11929
|
function log(msg) {
|
|
11421
11930
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").substring(0, 19);
|
|
11422
11931
|
const line = `[DAEMON ${ts}] ${msg}
|
|
@@ -11448,7 +11957,7 @@ async function main() {
|
|
|
11448
11957
|
if (err.code === "EADDRINUSE") {
|
|
11449
11958
|
log(`Port ${daemonPort} already in use \u2014 another daemon instance likely won the startup race. Exiting gracefully.`);
|
|
11450
11959
|
try {
|
|
11451
|
-
unlinkSync(
|
|
11960
|
+
unlinkSync(join9(CONFIG_DIR3, "daemon.json"));
|
|
11452
11961
|
} catch {
|
|
11453
11962
|
}
|
|
11454
11963
|
process.exit(0);
|
|
@@ -11490,7 +11999,7 @@ async function main() {
|
|
|
11490
11999
|
});
|
|
11491
12000
|
});
|
|
11492
12001
|
mkdirSync5(CONFIG_DIR3, { recursive: true });
|
|
11493
|
-
writeFileSync5(
|
|
12002
|
+
writeFileSync5(join9(CONFIG_DIR3, "daemon.json"), JSON.stringify({
|
|
11494
12003
|
port: daemonPort,
|
|
11495
12004
|
pid: process.pid,
|
|
11496
12005
|
startedAt: Date.now()
|