@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/cli.js
CHANGED
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
forwardSessionList,
|
|
23
23
|
forwardViewerCheckSelector,
|
|
24
24
|
version
|
|
25
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-SH6OTPXN.js";
|
|
26
26
|
import {
|
|
27
27
|
buildViewerUrl
|
|
28
28
|
} from "./chunk-3OD76SUE.js";
|
|
@@ -35,7 +35,7 @@ import {
|
|
|
35
35
|
} from "./chunk-Q2DFJQGS.js";
|
|
36
36
|
import {
|
|
37
37
|
SessionRecorder
|
|
38
|
-
} from "./chunk-
|
|
38
|
+
} from "./chunk-NODRQGOK.js";
|
|
39
39
|
import {
|
|
40
40
|
addKnownIssue,
|
|
41
41
|
getKnowledgePath,
|
|
@@ -59,7 +59,7 @@ import {
|
|
|
59
59
|
resolveLaunchOpts,
|
|
60
60
|
saveSessionDiskMeta,
|
|
61
61
|
setActivePage
|
|
62
|
-
} from "./chunk-
|
|
62
|
+
} from "./chunk-OVW2UEWN.js";
|
|
63
63
|
import "./chunk-TNEN6VQ2.js";
|
|
64
64
|
import {
|
|
65
65
|
errMsg
|
|
@@ -67,7 +67,7 @@ import {
|
|
|
67
67
|
import {
|
|
68
68
|
detectAntiBot,
|
|
69
69
|
formatDetectionMessage
|
|
70
|
-
} from "./chunk-
|
|
70
|
+
} from "./chunk-JKVUFP3G.js";
|
|
71
71
|
import {
|
|
72
72
|
NPM_REGISTRY_URL,
|
|
73
73
|
NPM_SCOPE,
|
|
@@ -75,8 +75,9 @@ import {
|
|
|
75
75
|
getMarketplaceUrl,
|
|
76
76
|
loadConfig,
|
|
77
77
|
resolveNpmPackageWithFallback,
|
|
78
|
+
resolveScreenshotsDir,
|
|
78
79
|
setConfigValue
|
|
79
|
-
} from "./chunk-
|
|
80
|
+
} from "./chunk-ZTHE5RBZ.js";
|
|
80
81
|
import "./chunk-KFQGP6VL.js";
|
|
81
82
|
|
|
82
83
|
// src/router.ts
|
|
@@ -199,8 +200,8 @@ function asZodSchema(value) {
|
|
|
199
200
|
|
|
200
201
|
// src/executor.ts
|
|
201
202
|
import {
|
|
202
|
-
ok as
|
|
203
|
-
fail as
|
|
203
|
+
ok as ok28,
|
|
204
|
+
fail as fail13,
|
|
204
205
|
isCommandResult,
|
|
205
206
|
CompositeStorage as CompositeStorage2,
|
|
206
207
|
TipCollector as TipCollector2,
|
|
@@ -460,14 +461,23 @@ var CAPTCHA_SELECTORS = {
|
|
|
460
461
|
generic: '#captcha, .captcha-container, [class*="captcha"]'
|
|
461
462
|
};
|
|
462
463
|
async function detectCaptcha(page) {
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
464
|
+
const hit = await page.evaluate(`
|
|
465
|
+
(function() {
|
|
466
|
+
var S = ${JSON.stringify(CAPTCHA_SELECTORS)};
|
|
467
|
+
for (var type in S) {
|
|
468
|
+
var sel = S[type];
|
|
469
|
+
var el;
|
|
470
|
+
try { el = document.querySelector(sel); } catch (e) { continue; }
|
|
471
|
+
if (el) {
|
|
472
|
+
var url = el.getAttribute && el.getAttribute('src');
|
|
473
|
+
return { type: type, selector: sel, iframeUrl: url || undefined };
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
return null;
|
|
477
|
+
})()
|
|
478
|
+
`).catch(() => null);
|
|
479
|
+
if (!hit) return null;
|
|
480
|
+
return { type: hit.type, selector: hit.selector, iframeUrl: hit.iframeUrl || void 0 };
|
|
471
481
|
}
|
|
472
482
|
async function waitForCaptchaSolved(page, captchaInfo, timeoutMs = 18e4) {
|
|
473
483
|
const startTime = Date.now();
|
|
@@ -518,12 +528,24 @@ var clickCommand = registerCommand({
|
|
|
518
528
|
let detectedNewPage;
|
|
519
529
|
let cleanup;
|
|
520
530
|
if (ctx.browserContext?.on) {
|
|
531
|
+
const targetIdOf = (p2) => p2._targetId;
|
|
532
|
+
let targetIdsBefore;
|
|
533
|
+
try {
|
|
534
|
+
targetIdsBefore = new Set(ctx.browserContext.pages().map(targetIdOf));
|
|
535
|
+
} catch {
|
|
536
|
+
targetIdsBefore = /* @__PURE__ */ new Set();
|
|
537
|
+
}
|
|
521
538
|
const pagePromise = new Promise((resolve10) => {
|
|
522
539
|
const timer = setTimeout(() => {
|
|
523
540
|
ctx.browserContext.off("page", handler);
|
|
524
541
|
resolve10(void 0);
|
|
525
542
|
}, 3e3);
|
|
526
543
|
const handler = (page2) => {
|
|
544
|
+
if (targetIdsBefore.has(targetIdOf(page2))) return;
|
|
545
|
+
try {
|
|
546
|
+
if ((page2.url?.() ?? "") === "about:blank") return;
|
|
547
|
+
} catch {
|
|
548
|
+
}
|
|
527
549
|
clearTimeout(timer);
|
|
528
550
|
ctx.browserContext.off("page", handler);
|
|
529
551
|
resolve10(page2);
|
|
@@ -924,9 +946,11 @@ var mouseCommand = registerCommand({
|
|
|
924
946
|
await ctx.page.mouse.move(p.x, p.y, { steps: p.steps || 1 });
|
|
925
947
|
break;
|
|
926
948
|
case "down":
|
|
949
|
+
await ctx.page.mouse.move(p.x, p.y);
|
|
927
950
|
await ctx.page.mouse.down({ button });
|
|
928
951
|
break;
|
|
929
952
|
case "up":
|
|
953
|
+
await ctx.page.mouse.move(p.x, p.y);
|
|
930
954
|
await ctx.page.mouse.up({ button });
|
|
931
955
|
break;
|
|
932
956
|
case "click":
|
|
@@ -940,28 +964,441 @@ var mouseCommand = registerCommand({
|
|
|
940
964
|
}
|
|
941
965
|
});
|
|
942
966
|
|
|
943
|
-
// src/commands/
|
|
967
|
+
// src/commands/find-visual.ts
|
|
944
968
|
import { z as z7 } from "zod";
|
|
945
|
-
import { ok as ok7,
|
|
969
|
+
import { ok as ok7, fail as fail2 } from "@dyyz1993/xcli-core";
|
|
970
|
+
import { readFileSync, existsSync } from "fs";
|
|
971
|
+
import { homedir as homedir2 } from "os";
|
|
972
|
+
import { join as join2 } from "path";
|
|
973
|
+
function loadVLMCredentials() {
|
|
974
|
+
const candidates = [
|
|
975
|
+
process.env.XBROWSER_VLM_CONFIG,
|
|
976
|
+
join2(homedir2(), ".zcode", "v2", "config.json")
|
|
977
|
+
].filter(Boolean);
|
|
978
|
+
for (const file of candidates) {
|
|
979
|
+
try {
|
|
980
|
+
if (!existsSync(file)) continue;
|
|
981
|
+
const cfg = JSON.parse(readFileSync(file, "utf8"));
|
|
982
|
+
const provider = Object.values(cfg.provider ?? {})[0];
|
|
983
|
+
if (provider?.options?.apiKey && provider.options.baseURL) {
|
|
984
|
+
const models = Object.keys(provider.models ?? {});
|
|
985
|
+
const vl = models.find((m) => /vl|vision/i.test(m));
|
|
986
|
+
const flash = models.find((m) => /flash/i.test(m) && /5\.?3/i.test(m));
|
|
987
|
+
return {
|
|
988
|
+
apiKey: provider.options.apiKey,
|
|
989
|
+
baseURL: provider.options.baseURL.replace(/\/$/, ""),
|
|
990
|
+
model: process.env.XBROWSER_VLM_MODEL || vl || flash || models.find((m) => /5\.[23]/.test(m)) || models[0]
|
|
991
|
+
};
|
|
992
|
+
}
|
|
993
|
+
} catch {
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
return null;
|
|
997
|
+
}
|
|
998
|
+
async function locateByVLM(creds, imageBase64, target, feedback) {
|
|
999
|
+
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}"""
|
|
1000
|
+
|
|
1001
|
+
\u4E25\u683C\u53EA\u8F93\u51FA\u4E00\u4E2A JSON \u5BF9\u8C61\uFF08\u65E0 markdown\u3001\u65E0\u89E3\u91CA\uFF09\uFF1A
|
|
1002
|
+
{"label":"<\u5143\u7D20\u540D>","box":{"x":<\u5DE6\u4E0Ax>,"y":<\u5DE6\u4E0Ay>,"width":<\u5BBD>,"height":<\u9AD8>},"confidence":"high|medium|low"}
|
|
1003
|
+
\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
|
|
1004
|
+
${feedback ? `\u6CE8\u610F\uFF1A${feedback}` : ""}`;
|
|
1005
|
+
const resp = await fetch(creds.baseURL + "/v1/messages", {
|
|
1006
|
+
method: "POST",
|
|
1007
|
+
headers: { "content-type": "application/json", "x-api-key": creds.apiKey, "anthropic-version": "2023-06-01" },
|
|
1008
|
+
body: JSON.stringify({
|
|
1009
|
+
model: creds.model,
|
|
1010
|
+
max_tokens: 2e3,
|
|
1011
|
+
// thinking 模型的思考块也计入配额,300 会在 text 前截断
|
|
1012
|
+
messages: [{
|
|
1013
|
+
role: "user",
|
|
1014
|
+
content: [
|
|
1015
|
+
{ type: "image", source: { type: "base64", media_type: "image/png", data: imageBase64 } },
|
|
1016
|
+
{ type: "text", text: prompt }
|
|
1017
|
+
]
|
|
1018
|
+
}]
|
|
1019
|
+
}),
|
|
1020
|
+
signal: AbortSignal.timeout(6e4)
|
|
1021
|
+
});
|
|
1022
|
+
if (!resp.ok) throw new Error(`VLM HTTP ${resp.status}`);
|
|
1023
|
+
const data = await resp.json();
|
|
1024
|
+
const text = (data.content ?? []).filter((c) => c.type === "text").map((c) => c.text ?? "").join("");
|
|
1025
|
+
const m = text.match(/\{[\s\S]*\}/);
|
|
1026
|
+
if (!m) {
|
|
1027
|
+
console.error("[find-visual] VLM raw (no JSON):", text.substring(0, 200));
|
|
1028
|
+
return null;
|
|
1029
|
+
}
|
|
1030
|
+
const hit = JSON.parse(m[0]);
|
|
1031
|
+
if (!hit.label || !hit.box) return null;
|
|
1032
|
+
return hit;
|
|
1033
|
+
}
|
|
1034
|
+
var findVisualCommand = registerCommand({
|
|
1035
|
+
name: "find-visual",
|
|
1036
|
+
description: "Visual element location: screenshot \u2192 VLM \u2192 page coordinates (optionally click)",
|
|
1037
|
+
scope: "page",
|
|
1038
|
+
parameters: z7.object({
|
|
1039
|
+
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'),
|
|
1040
|
+
click: z7.boolean().optional().describe("\u627E\u5230\u540E\u7ACB\u5373\u70B9\u51FB\uFF08\u771F\u5B9E\u9F20\u6807\u4E8B\u4EF6\uFF09"),
|
|
1041
|
+
threshold: z7.coerce.number().optional().describe("\u7F6E\u4FE1\u5EA6\u9608\u503C\uFF0C\u4F4E\u4E8E\u5219\u62A5 not-found\uFF08\u9ED8\u8BA4 0\uFF09"),
|
|
1042
|
+
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")
|
|
1043
|
+
}),
|
|
1044
|
+
result: z7.object({
|
|
1045
|
+
found: z7.boolean(),
|
|
1046
|
+
element: z7.string(),
|
|
1047
|
+
box: z7.unknown().optional(),
|
|
1048
|
+
center: z7.unknown().optional(),
|
|
1049
|
+
confidence: z7.string().optional(),
|
|
1050
|
+
model: z7.string().optional(),
|
|
1051
|
+
clicked: z7.boolean().optional(),
|
|
1052
|
+
screenshot: z7.string().optional()
|
|
1053
|
+
}),
|
|
1054
|
+
handler: async (p, ctx) => {
|
|
1055
|
+
const page = ctx.page;
|
|
1056
|
+
const creds = loadVLMCredentials();
|
|
1057
|
+
if (!creds) {
|
|
1058
|
+
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", [
|
|
1059
|
+
"\u4E5F\u53EF\u7528 XBROWSER_VLM_MODEL \u6307\u5B9A\u6A21\u578B\uFF08\u9ED8\u8BA4\u81EA\u52A8\u9009 VL/\u65D7\u8230\u6A21\u578B\uFF09"
|
|
1060
|
+
]);
|
|
1061
|
+
}
|
|
1062
|
+
const buffer = await page.screenshot({ type: "png" });
|
|
1063
|
+
const imageBase64 = buffer.toString("base64");
|
|
1064
|
+
const imgW = buffer.readUInt32BE(16);
|
|
1065
|
+
const imgH = buffer.readUInt32BE(20);
|
|
1066
|
+
let hit = null;
|
|
1067
|
+
let feedback = "";
|
|
1068
|
+
try {
|
|
1069
|
+
for (let attempt = 0; attempt < 3 && !hit; attempt++) {
|
|
1070
|
+
const candidate = await locateByVLM(creds, imageBase64, p.element, feedback);
|
|
1071
|
+
if (!candidate) break;
|
|
1072
|
+
const b = candidate.box;
|
|
1073
|
+
const inBounds = b.x >= 0 && b.y >= 0 && b.x + b.width <= imgW + 2 && b.y + b.height <= imgH + 2;
|
|
1074
|
+
const sane = b.width > 2 && b.height > 2;
|
|
1075
|
+
if (inBounds && sane) {
|
|
1076
|
+
hit = candidate;
|
|
1077
|
+
break;
|
|
1078
|
+
}
|
|
1079
|
+
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`;
|
|
1080
|
+
}
|
|
1081
|
+
} catch (e) {
|
|
1082
|
+
return fail2(`VLM \u8C03\u7528\u5931\u8D25: ${e instanceof Error ? e.message : String(e)}`);
|
|
1083
|
+
}
|
|
1084
|
+
if (!hit) {
|
|
1085
|
+
return ok7({ found: false, element: p.element, model: creds.model }, [
|
|
1086
|
+
`\u622A\u56FE\u4E2D\u672A\u627E\u5230: ${p.element}`
|
|
1087
|
+
]);
|
|
1088
|
+
}
|
|
1089
|
+
if (p.zoom !== false && hit) {
|
|
1090
|
+
try {
|
|
1091
|
+
const pad = Math.max(20, Math.round(Math.max(hit.box.width, hit.box.height) * 0.5));
|
|
1092
|
+
const clip = {
|
|
1093
|
+
x: Math.max(0, hit.box.x - pad),
|
|
1094
|
+
y: Math.max(0, hit.box.y - pad),
|
|
1095
|
+
width: Math.min(imgW - Math.max(0, hit.box.x - pad), hit.box.width + pad * 2),
|
|
1096
|
+
height: Math.min(imgH - Math.max(0, hit.box.y - pad), hit.box.height + pad * 2)
|
|
1097
|
+
};
|
|
1098
|
+
if (clip.width > 8 && clip.height > 8) {
|
|
1099
|
+
const zoomBuf = await page.screenshot({ type: "png", clip });
|
|
1100
|
+
const zoomB64 = zoomBuf.toString("base64");
|
|
1101
|
+
const zoomW = zoomBuf.readUInt32BE(16);
|
|
1102
|
+
const zoomH = zoomBuf.readUInt32BE(20);
|
|
1103
|
+
const refined = await locateByVLM(creds, zoomB64, p.element);
|
|
1104
|
+
if (refined) {
|
|
1105
|
+
const rb = refined.box;
|
|
1106
|
+
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;
|
|
1107
|
+
if (inZoom) {
|
|
1108
|
+
hit = {
|
|
1109
|
+
label: refined.label || hit.label,
|
|
1110
|
+
box: {
|
|
1111
|
+
x: clip.x + rb.x,
|
|
1112
|
+
y: clip.y + rb.y,
|
|
1113
|
+
width: rb.width,
|
|
1114
|
+
height: rb.height
|
|
1115
|
+
},
|
|
1116
|
+
confidence: refined.confidence === "low" ? hit.confidence : refined.confidence
|
|
1117
|
+
};
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
} catch {
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
if (p.threshold !== void 0) {
|
|
1125
|
+
const rank = { high: 3, medium: 2, low: 1 };
|
|
1126
|
+
const thresholdName = p.threshold >= 3 ? "high" : p.threshold >= 2 ? "medium" : "low";
|
|
1127
|
+
if ((rank[hit.confidence ?? "low"] ?? 1) < (rank[thresholdName] ?? 1)) {
|
|
1128
|
+
return ok7({ found: false, element: p.element, confidence: hit.confidence, box: hit.box, model: creds.model }, [
|
|
1129
|
+
`\u7F6E\u4FE1\u5EA6 ${hit.confidence} \u4F4E\u4E8E\u9608\u503C ${p.threshold}`
|
|
1130
|
+
]);
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
const viewport = await page.evaluate("JSON.stringify({w: window.innerWidth, h: window.innerHeight})").then((v) => JSON.parse(String(v))).catch(() => ({ w: 1280, h: 800 }));
|
|
1134
|
+
const pngW = buffer.readUInt32BE(16);
|
|
1135
|
+
const pngH = buffer.readUInt32BE(20);
|
|
1136
|
+
const scaleX = viewport.w / pngW;
|
|
1137
|
+
const scaleY = viewport.h / pngH;
|
|
1138
|
+
const center = {
|
|
1139
|
+
x: Math.round((hit.box.x + hit.box.width / 2) * scaleX),
|
|
1140
|
+
y: Math.round((hit.box.y + hit.box.height / 2) * scaleY)
|
|
1141
|
+
};
|
|
1142
|
+
let clicked = false;
|
|
1143
|
+
if (p.click) {
|
|
1144
|
+
await page.mouse.click(center.x, center.y, { stealth: true });
|
|
1145
|
+
clicked = true;
|
|
1146
|
+
}
|
|
1147
|
+
return ok7({
|
|
1148
|
+
found: true,
|
|
1149
|
+
element: p.element,
|
|
1150
|
+
box: hit.box,
|
|
1151
|
+
center,
|
|
1152
|
+
confidence: hit.confidence,
|
|
1153
|
+
model: creds.model,
|
|
1154
|
+
clicked,
|
|
1155
|
+
screenshot: `data:image/png;base64,${imageBase64.substring(0, 64)}... (${Math.round(imageBase64.length / 1024)}KB)`
|
|
1156
|
+
}, [
|
|
1157
|
+
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`
|
|
1158
|
+
]);
|
|
1159
|
+
}
|
|
1160
|
+
});
|
|
1161
|
+
|
|
1162
|
+
// src/commands/vision-task.ts
|
|
1163
|
+
import { z as z8 } from "zod";
|
|
1164
|
+
import { ok as ok8, fail as fail3 } from "@dyyz1993/xcli-core";
|
|
1165
|
+
import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
|
|
1166
|
+
import { homedir as homedir3 } from "os";
|
|
1167
|
+
import { join as join3 } from "path";
|
|
1168
|
+
function loadVLMCredentials2() {
|
|
1169
|
+
const candidates = [
|
|
1170
|
+
process.env.XBROWSER_VLM_CONFIG,
|
|
1171
|
+
join3(homedir3(), ".zcode", "v2", "config.json")
|
|
1172
|
+
].filter(Boolean);
|
|
1173
|
+
for (const file of candidates) {
|
|
1174
|
+
try {
|
|
1175
|
+
if (!existsSync2(file)) continue;
|
|
1176
|
+
const cfg = JSON.parse(readFileSync2(file, "utf8"));
|
|
1177
|
+
const provider = Object.values(cfg.provider ?? {})[0];
|
|
1178
|
+
if (provider?.options?.apiKey && provider.options.baseURL) {
|
|
1179
|
+
const models = Object.keys(provider.models ?? {});
|
|
1180
|
+
const vl = models.find((m) => /vl|vision/i.test(m));
|
|
1181
|
+
const flash = models.find((m) => /flash/i.test(m) && /5\.?3/i.test(m));
|
|
1182
|
+
return {
|
|
1183
|
+
apiKey: provider.options.apiKey,
|
|
1184
|
+
baseURL: provider.options.baseURL.replace(/\/$/, ""),
|
|
1185
|
+
model: process.env.XBROWSER_VLM_MODEL || vl || flash || models.find((m) => /5\.[23]/.test(m)) || models[0]
|
|
1186
|
+
};
|
|
1187
|
+
}
|
|
1188
|
+
} catch {
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
return null;
|
|
1192
|
+
}
|
|
1193
|
+
async function vlmAsk(creds, content, maxTokens = 2e3) {
|
|
1194
|
+
const resp = await fetch(creds.baseURL + "/v1/messages", {
|
|
1195
|
+
method: "POST",
|
|
1196
|
+
headers: { "content-type": "application/json", "x-api-key": creds.apiKey, "anthropic-version": "2023-06-01" },
|
|
1197
|
+
body: JSON.stringify({ model: creds.model, max_tokens: maxTokens, messages: [{ role: "user", content }] }),
|
|
1198
|
+
signal: AbortSignal.timeout(9e4)
|
|
1199
|
+
});
|
|
1200
|
+
if (!resp.ok) throw new Error(`VLM HTTP ${resp.status}`);
|
|
1201
|
+
const data = await resp.json();
|
|
1202
|
+
return (data.content ?? []).filter((c) => c.type === "text").map((c) => c.text ?? "").join("");
|
|
1203
|
+
}
|
|
1204
|
+
async function vlmAskRetry(creds, content, maxTokens = 2e3) {
|
|
1205
|
+
let last = "";
|
|
1206
|
+
for (let i = 0; i < 2; i++) {
|
|
1207
|
+
last = await vlmAsk(creds, content, maxTokens);
|
|
1208
|
+
if (last.trim()) return last;
|
|
1209
|
+
await new Promise((r) => setTimeout(r, 1200));
|
|
1210
|
+
}
|
|
1211
|
+
return last;
|
|
1212
|
+
}
|
|
1213
|
+
function pngDims(b64) {
|
|
1214
|
+
const buf = Buffer.from(b64, "base64");
|
|
1215
|
+
return { w: buf.readUInt32BE(16), h: buf.readUInt32BE(20) };
|
|
1216
|
+
}
|
|
1217
|
+
function parseDecision(text) {
|
|
1218
|
+
const m = text.match(/\{[\s\S]*\}/);
|
|
1219
|
+
if (!m) return null;
|
|
1220
|
+
try {
|
|
1221
|
+
const j = JSON.parse(m[0]);
|
|
1222
|
+
if (!j.action || !j.action.type) return null;
|
|
1223
|
+
return j;
|
|
1224
|
+
} catch {
|
|
1225
|
+
return null;
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
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
|
|
1229
|
+
|
|
1230
|
+
\u4E25\u683C\u53EA\u8F93\u51FA\u4E00\u4E2A JSON\uFF08\u65E0 markdown\uFF09\uFF1A
|
|
1231
|
+
{"thought":"<\u7B80\u77ED\u63A8\u7406>","action":{"type":"click","x":<int>,"y":<int>}
|
|
1232
|
+
\u6216 {"type":"type","text":"<\u8981\u8F93\u5165\u7684\u6587\u5B57>"}\uFF08\u8F93\u5165\u524D\u987B\u5DF2\u70B9\u51FB\u805A\u7126\u8F93\u5165\u6846\uFF09
|
|
1233
|
+
\u6216 {"type":"key","key":"Enter"}\uFF08key \u540D\uFF1AEnter/Tab/Escape/ArrowDown\u2026\uFF09
|
|
1234
|
+
\u6216 {"type":"scroll","direction":"down","amount":<px>}
|
|
1235
|
+
\u6216 {"type":"done","summary":"<\u4EFB\u52A1\u5B8C\u6210\u6458\u8981>"}
|
|
1236
|
+
\u6216 {"type":"stuck","reason":"<\u65E0\u6CD5\u7EE7\u7EED\u7684\u539F\u56E0>"}}
|
|
1237
|
+
|
|
1238
|
+
\u5750\u6807\u4EE5\u622A\u56FE\u5DE6\u4E0A\u89D2\u4E3A\u539F\u70B9\u3001\u50CF\u7D20\u4E3A\u5355\u4F4D\u3002\u89C4\u5219\uFF1A
|
|
1239
|
+
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
|
|
1240
|
+
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
|
|
1241
|
+
3. \u4EFB\u52A1\u76EE\u6807\u8FBE\u6210\u7ACB\u5373\u8F93\u51FA done\uFF1B5 \u6B65\u65E0\u8FDB\u5C55\u8F93\u51FA stuck`;
|
|
1242
|
+
var visionTaskCommand = registerCommand({
|
|
1243
|
+
name: "vision-task",
|
|
1244
|
+
description: "Vision-loop agent: screenshot \u2192 VLM decide \u2192 CDP act \u2192 verify, until done",
|
|
1245
|
+
scope: "page",
|
|
1246
|
+
parameters: z8.object({
|
|
1247
|
+
task: z8.string().describe('\u81EA\u7136\u8BED\u8A00\u4EFB\u52A1\uFF0C\u5982 "\u641C\u7D22 xbrowser \u5E76\u6253\u5F00\u7B2C\u4E00\u7BC7\u6587\u7AE0"'),
|
|
1248
|
+
maxSteps: z8.coerce.number().optional().describe("\u6700\u5927\u6B65\u6570\uFF08\u9ED8\u8BA4 12\uFF09"),
|
|
1249
|
+
settleMs: z8.coerce.number().optional().describe("\u6BCF\u6B65\u540E\u7B49\u5F85\u9875\u9762\u7A33\u5B9A\u7684\u6BEB\u79D2\u6570\uFF08\u9ED8\u8BA4 1500\uFF09")
|
|
1250
|
+
}),
|
|
1251
|
+
result: z8.object({
|
|
1252
|
+
done: z8.boolean(),
|
|
1253
|
+
steps: z8.unknown().optional(),
|
|
1254
|
+
summary: z8.string().optional(),
|
|
1255
|
+
model: z8.string().optional()
|
|
1256
|
+
}),
|
|
1257
|
+
handler: async (p, ctx) => {
|
|
1258
|
+
let page = ctx.page;
|
|
1259
|
+
const creds = loadVLMCredentials2();
|
|
1260
|
+
if (!creds) {
|
|
1261
|
+
return fail3("\u672A\u627E\u5230 VLM \u51ED\u636E\uFF1A\u9700\u8981 ~/.zcode/v2/config.json \u6216 XBROWSER_VLM_CONFIG");
|
|
1262
|
+
}
|
|
1263
|
+
const maxSteps = p.maxSteps ?? 12;
|
|
1264
|
+
const settleMs = p.settleMs ?? 1500;
|
|
1265
|
+
const steps = [];
|
|
1266
|
+
const history = [];
|
|
1267
|
+
let lastUrl = page.url();
|
|
1268
|
+
for (let step = 1; step <= maxSteps; step++) {
|
|
1269
|
+
if (page.url() === "about:blank") {
|
|
1270
|
+
try {
|
|
1271
|
+
const ctxObj = page.context?.();
|
|
1272
|
+
const pages = ctxObj?.pages?.() || [];
|
|
1273
|
+
let real;
|
|
1274
|
+
for (let i = pages.length - 1; i >= 0; i--) {
|
|
1275
|
+
const u = pages[i]?.url?.() ?? "";
|
|
1276
|
+
if (u && u !== "about:blank") {
|
|
1277
|
+
real = pages[i];
|
|
1278
|
+
break;
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
if (real) {
|
|
1282
|
+
await real.bringToFront().catch(() => {
|
|
1283
|
+
});
|
|
1284
|
+
page = real;
|
|
1285
|
+
}
|
|
1286
|
+
} catch {
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
const buffer = await page.screenshot({ type: "png" });
|
|
1290
|
+
const b64 = buffer.toString("base64");
|
|
1291
|
+
const { w, h } = pngDims(b64);
|
|
1292
|
+
const obsText = [
|
|
1293
|
+
`\u4EFB\u52A1\uFF1A${p.task}`,
|
|
1294
|
+
`\u622A\u56FE\u5C3A\u5BF8\uFF1A${w}x${h}\uFF08\u5750\u6807\u539F\u70B9\uFF1A\u5DE6\u4E0A\u89D2\uFF09`,
|
|
1295
|
+
`\u5F53\u524D URL\uFF1A${page.url()}`,
|
|
1296
|
+
history.length ? `\u5DF2\u6267\u884C\u52A8\u4F5C\uFF1A
|
|
1297
|
+
${history.slice(-6).join("\n")}` : "\u5C1A\u65E0\u52A8\u4F5C\u3002"
|
|
1298
|
+
].join("\n");
|
|
1299
|
+
const reply = await vlmAskRetry(creds, [
|
|
1300
|
+
{ type: "image", source: { type: "base64", media_type: "image/png", data: b64 } },
|
|
1301
|
+
{ type: "text", text: `${SYSTEM_PROMPT}
|
|
1302
|
+
|
|
1303
|
+
${obsText}` }
|
|
1304
|
+
]);
|
|
1305
|
+
const decision = parseDecision(reply);
|
|
1306
|
+
if (!decision) {
|
|
1307
|
+
steps.push({ n: step, thought: "(unparseable)", action: "stop", ts: Date.now() });
|
|
1308
|
+
return ok8({ done: false, steps, summary: `\u7B2C ${step} \u6B65 VLM \u8F93\u51FA\u65E0\u6CD5\u89E3\u6790: ${reply.substring(0, 120)}`, model: creds.model });
|
|
1309
|
+
}
|
|
1310
|
+
const a = decision.action;
|
|
1311
|
+
let acted = "";
|
|
1312
|
+
try {
|
|
1313
|
+
if (a.type === "done") {
|
|
1314
|
+
steps.push({ n: step, thought: decision.thought, action: "done", ts: Date.now() });
|
|
1315
|
+
return ok8({ done: true, steps, summary: a.summary, model: creds.model });
|
|
1316
|
+
}
|
|
1317
|
+
if (a.type === "stuck") {
|
|
1318
|
+
steps.push({ n: step, thought: decision.thought, action: "stuck", ts: Date.now() });
|
|
1319
|
+
return ok8({ done: false, steps, summary: `stuck: ${a.reason}`, model: creds.model });
|
|
1320
|
+
}
|
|
1321
|
+
if (a.type === "click") {
|
|
1322
|
+
const cx = Math.max(2, Math.min(w - 2, Math.round(a.x)));
|
|
1323
|
+
const cy = Math.max(2, Math.min(h - 2, Math.round(a.y)));
|
|
1324
|
+
await page.mouse.click(cx, cy, { stealth: true });
|
|
1325
|
+
acted = `click(${cx},${cy})`;
|
|
1326
|
+
} else if (a.type === "type") {
|
|
1327
|
+
await page.keyboard.type(a.text, { stealth: true });
|
|
1328
|
+
acted = `type("${a.text.substring(0, 30)}")`;
|
|
1329
|
+
} else if (a.type === "key") {
|
|
1330
|
+
await page.keyboard.press(a.key);
|
|
1331
|
+
acted = `key(${a.key})`;
|
|
1332
|
+
} else if (a.type === "scroll") {
|
|
1333
|
+
await page.mouse.wheel(0, a.direction === "down" ? a.amount : -a.amount);
|
|
1334
|
+
acted = `scroll(${a.direction},${a.amount})`;
|
|
1335
|
+
}
|
|
1336
|
+
} catch (e) {
|
|
1337
|
+
acted = `${a.type} FAILED: ${e instanceof Error ? e.message : String(e).substring(0, 80)}`;
|
|
1338
|
+
}
|
|
1339
|
+
steps.push({ n: step, thought: decision.thought, action: acted, ts: Date.now() });
|
|
1340
|
+
history.push(`${step}. ${acted} \u2014 ${decision.thought.substring(0, 60)}`);
|
|
1341
|
+
await new Promise((r) => setTimeout(r, settleMs));
|
|
1342
|
+
if (a.type === "click") {
|
|
1343
|
+
try {
|
|
1344
|
+
const ctxObj = page.context?.();
|
|
1345
|
+
const pages = ctxObj?.pages?.() || [];
|
|
1346
|
+
for (let i = pages.length - 1; i >= 0; i--) {
|
|
1347
|
+
const cand = pages[i];
|
|
1348
|
+
if (!cand || cand === page) continue;
|
|
1349
|
+
const u = cand.url?.() ?? "";
|
|
1350
|
+
if (!u || u === "about:blank") continue;
|
|
1351
|
+
if (u === page.url()) break;
|
|
1352
|
+
await cand.bringToFront().catch(() => {
|
|
1353
|
+
});
|
|
1354
|
+
page = cand;
|
|
1355
|
+
break;
|
|
1356
|
+
}
|
|
1357
|
+
} catch {
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
const urlNow = page.url();
|
|
1361
|
+
if (urlNow !== lastUrl) {
|
|
1362
|
+
history.push(` [\u9875\u9762\u8DF3\u8F6C \u2192 ${urlNow.substring(0, 80)}]`);
|
|
1363
|
+
lastUrl = urlNow;
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
return ok8({ done: false, steps, summary: `\u5DF2\u8FBE\u6700\u5927\u6B65\u6570 ${maxSteps}`, model: creds.model });
|
|
1367
|
+
}
|
|
1368
|
+
});
|
|
1369
|
+
|
|
1370
|
+
// src/commands/evaluate.ts
|
|
1371
|
+
import { z as z9 } from "zod";
|
|
1372
|
+
import { ok as ok9, normalizeTips as normalizeTips2 } from "@dyyz1993/xcli-core";
|
|
946
1373
|
var evaluateCommand = registerCommand({
|
|
947
1374
|
name: "eval",
|
|
948
|
-
description: "Evaluate JavaScript expression in the browser",
|
|
1375
|
+
description: "Evaluate JavaScript expression in the browser (--frame <url-substring> \u53EF\u5B9A\u5411\u5230 iframe \u4E0A\u4E0B\u6587\uFF0C\u8DE8\u57DF iframe \u4EA6\u53EF)",
|
|
949
1376
|
scope: "page",
|
|
950
|
-
parameters:
|
|
951
|
-
expression:
|
|
1377
|
+
parameters: z9.object({
|
|
1378
|
+
expression: z9.string().optional(),
|
|
1379
|
+
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"),
|
|
1380
|
+
// Internal: base64url-encoded script set by the router for `eval <js...>`,
|
|
1381
|
+
// so multiline/semicolon JS survives chain parsing (which splits on ';'
|
|
1382
|
+
// and whitespace). Not intended for direct CLI use.
|
|
1383
|
+
"script-b64": z9.string().optional().describe("Internal: base64url script set by the CLI router; do not pass directly")
|
|
952
1384
|
}),
|
|
953
1385
|
handler: async (p, ctx) => {
|
|
1386
|
+
const rawScript = p["script-b64"];
|
|
1387
|
+
const expression = rawScript ? Buffer.from(rawScript, "base64url").toString("utf8") : p.expression;
|
|
1388
|
+
if (!expression || !expression.trim()) {
|
|
1389
|
+
return { ok: false, error: "eval: expression is required" };
|
|
1390
|
+
}
|
|
954
1391
|
const engine = createRuleEngine();
|
|
955
1392
|
engine.start();
|
|
956
1393
|
const decision = engine.evaluate({
|
|
957
1394
|
method: "Runtime.evaluate",
|
|
958
|
-
params: { expression
|
|
1395
|
+
params: { expression },
|
|
959
1396
|
sessionId: "eval-check",
|
|
960
1397
|
direction: "client\u2192browser"
|
|
961
1398
|
});
|
|
962
1399
|
engine.stop();
|
|
963
|
-
const result = await ctx.page.
|
|
964
|
-
const response =
|
|
1400
|
+
const result = p.frame ? await ctx.page.evaluateInFrame(p.frame, expression) : await ctx.page.evaluate(expression);
|
|
1401
|
+
const response = ok9({ result });
|
|
965
1402
|
if (decision && decision.severity === "danger") {
|
|
966
1403
|
response.tips = normalizeTips2([`${decision.suggestion}`]);
|
|
967
1404
|
}
|
|
@@ -969,37 +1406,65 @@ var evaluateCommand = registerCommand({
|
|
|
969
1406
|
}
|
|
970
1407
|
});
|
|
971
1408
|
|
|
1409
|
+
// src/commands/ua.ts
|
|
1410
|
+
import { z as z10 } from "zod";
|
|
1411
|
+
import { ok as ok10 } from "@dyyz1993/xcli-core";
|
|
1412
|
+
function uaHookScript(ua) {
|
|
1413
|
+
const uaJson = JSON.stringify(ua);
|
|
1414
|
+
const appVersion = JSON.stringify(ua.replace(/^Mozilla\//, ""));
|
|
1415
|
+
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){}`;
|
|
1416
|
+
}
|
|
1417
|
+
var uaCommand = registerCommand({
|
|
1418
|
+
name: "ua",
|
|
1419
|
+
description: "Get or override the session User-Agent (anti-bot UA detection countermeasure)",
|
|
1420
|
+
scope: "page",
|
|
1421
|
+
parameters: z10.object({
|
|
1422
|
+
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')
|
|
1423
|
+
}),
|
|
1424
|
+
handler: async (p, ctx) => {
|
|
1425
|
+
if (!p.value) {
|
|
1426
|
+
const current = await ctx.page.evaluate("navigator.userAgent");
|
|
1427
|
+
return ok10({ userAgent: current });
|
|
1428
|
+
}
|
|
1429
|
+
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;
|
|
1430
|
+
const script = uaHookScript(ua);
|
|
1431
|
+
await ctx.page.addInitScript(script);
|
|
1432
|
+
await ctx.page.evaluate(`(function(){${script}})()`);
|
|
1433
|
+
return ok10({ userAgent: ua, mode: "init-script" });
|
|
1434
|
+
}
|
|
1435
|
+
});
|
|
1436
|
+
|
|
972
1437
|
// src/commands/storage.ts
|
|
973
|
-
import { z as
|
|
974
|
-
import { ok as
|
|
1438
|
+
import { z as z11 } from "zod";
|
|
1439
|
+
import { ok as ok11, fail as fail4 } from "@dyyz1993/xcli-core";
|
|
975
1440
|
var getCookiesCommand = registerCommand({
|
|
976
1441
|
name: "get-cookies",
|
|
977
1442
|
description: "Get all cookies for the current page",
|
|
978
1443
|
scope: "page",
|
|
979
|
-
result:
|
|
980
|
-
cookies:
|
|
1444
|
+
result: z11.object({
|
|
1445
|
+
cookies: z11.array(z11.record(z11.unknown()))
|
|
981
1446
|
}),
|
|
982
1447
|
handler: async (_p, ctx) => {
|
|
983
1448
|
const cookies = await ctx.browserContext.cookies();
|
|
984
|
-
return
|
|
1449
|
+
return ok11({ cookies });
|
|
985
1450
|
}
|
|
986
1451
|
});
|
|
987
1452
|
var setCookieCommand = registerCommand({
|
|
988
1453
|
name: "set-cookie",
|
|
989
1454
|
description: "Set a cookie",
|
|
990
1455
|
scope: "page",
|
|
991
|
-
parameters:
|
|
992
|
-
name:
|
|
993
|
-
value:
|
|
994
|
-
domain:
|
|
995
|
-
path:
|
|
996
|
-
url:
|
|
997
|
-
expires:
|
|
998
|
-
httpOnly:
|
|
999
|
-
secure:
|
|
1000
|
-
sameSite:
|
|
1456
|
+
parameters: z11.object({
|
|
1457
|
+
name: z11.coerce.string(),
|
|
1458
|
+
value: z11.coerce.string(),
|
|
1459
|
+
domain: z11.coerce.string().optional(),
|
|
1460
|
+
path: z11.coerce.string().optional(),
|
|
1461
|
+
url: z11.string().optional().describe("Cookie URL (alternative to domain)"),
|
|
1462
|
+
expires: z11.number().optional(),
|
|
1463
|
+
httpOnly: z11.boolean().optional(),
|
|
1464
|
+
secure: z11.boolean().optional(),
|
|
1465
|
+
sameSite: z11.enum(["Strict", "Lax", "None"]).optional()
|
|
1001
1466
|
}),
|
|
1002
|
-
result:
|
|
1467
|
+
result: z11.object({ name: z11.string() }),
|
|
1003
1468
|
handler: async (p, ctx) => {
|
|
1004
1469
|
const cookie = { ...p };
|
|
1005
1470
|
if (!cookie.domain && !cookie.url) {
|
|
@@ -1014,38 +1479,38 @@ var setCookieCommand = registerCommand({
|
|
|
1014
1479
|
}
|
|
1015
1480
|
}
|
|
1016
1481
|
if (!cookie.domain && !cookie.url) {
|
|
1017
|
-
return
|
|
1482
|
+
return fail4("set-cookie requires --domain or --url, or a non-blank page URL to infer from");
|
|
1018
1483
|
}
|
|
1019
1484
|
await ctx.browserContext.addCookies([cookie]);
|
|
1020
|
-
return
|
|
1485
|
+
return ok11({ name: p.name });
|
|
1021
1486
|
}
|
|
1022
1487
|
});
|
|
1023
1488
|
var clearCookiesCommand = registerCommand({
|
|
1024
1489
|
name: "clear-cookies",
|
|
1025
1490
|
description: "Clear all cookies",
|
|
1026
1491
|
scope: "page",
|
|
1027
|
-
result:
|
|
1492
|
+
result: z11.object({ cleared: z11.boolean() }),
|
|
1028
1493
|
handler: async (_p, ctx) => {
|
|
1029
1494
|
await ctx.browserContext.clearCookies();
|
|
1030
|
-
return
|
|
1495
|
+
return ok11({ cleared: true });
|
|
1031
1496
|
}
|
|
1032
1497
|
});
|
|
1033
1498
|
var getLocalStorageCommand = registerCommand({
|
|
1034
1499
|
name: "get-local-storage",
|
|
1035
1500
|
description: "Get localStorage entries",
|
|
1036
1501
|
scope: "page",
|
|
1037
|
-
parameters:
|
|
1038
|
-
key:
|
|
1502
|
+
parameters: z11.object({
|
|
1503
|
+
key: z11.string().optional()
|
|
1039
1504
|
}),
|
|
1040
|
-
result:
|
|
1041
|
-
|
|
1042
|
-
|
|
1505
|
+
result: z11.union([
|
|
1506
|
+
z11.object({ key: z11.string(), value: z11.string().nullable() }),
|
|
1507
|
+
z11.object({ data: z11.record(z11.string()) })
|
|
1043
1508
|
]),
|
|
1044
1509
|
handler: async (p, ctx) => {
|
|
1045
1510
|
try {
|
|
1046
1511
|
if (p.key) {
|
|
1047
1512
|
const value = await ctx.page.evaluate((k) => localStorage.getItem(k), p.key);
|
|
1048
|
-
return
|
|
1513
|
+
return ok11({ key: p.key, value });
|
|
1049
1514
|
}
|
|
1050
1515
|
const data = await ctx.page.evaluate(() => {
|
|
1051
1516
|
const entries = {};
|
|
@@ -1055,9 +1520,9 @@ var getLocalStorageCommand = registerCommand({
|
|
|
1055
1520
|
}
|
|
1056
1521
|
return entries;
|
|
1057
1522
|
});
|
|
1058
|
-
return
|
|
1523
|
+
return ok11({ data });
|
|
1059
1524
|
} catch (e) {
|
|
1060
|
-
return
|
|
1525
|
+
return fail4(`localStorage not accessible on this page: ${e instanceof Error ? e.message : String(e)}`);
|
|
1061
1526
|
}
|
|
1062
1527
|
}
|
|
1063
1528
|
});
|
|
@@ -1065,11 +1530,11 @@ var setLocalStorageCommand = registerCommand({
|
|
|
1065
1530
|
name: "set-local-storage",
|
|
1066
1531
|
description: "Set a localStorage entry",
|
|
1067
1532
|
scope: "page",
|
|
1068
|
-
parameters:
|
|
1069
|
-
key:
|
|
1070
|
-
value:
|
|
1533
|
+
parameters: z11.object({
|
|
1534
|
+
key: z11.string(),
|
|
1535
|
+
value: z11.string()
|
|
1071
1536
|
}),
|
|
1072
|
-
result:
|
|
1537
|
+
result: z11.object({ key: z11.string() }),
|
|
1073
1538
|
handler: async (p, ctx) => {
|
|
1074
1539
|
try {
|
|
1075
1540
|
await ctx.page.evaluate(
|
|
@@ -1078,9 +1543,9 @@ var setLocalStorageCommand = registerCommand({
|
|
|
1078
1543
|
},
|
|
1079
1544
|
{ key: p.key, value: p.value }
|
|
1080
1545
|
);
|
|
1081
|
-
return
|
|
1546
|
+
return ok11({ key: p.key });
|
|
1082
1547
|
} catch (e) {
|
|
1083
|
-
return
|
|
1548
|
+
return fail4(`localStorage not accessible on this page: ${e instanceof Error ? e.message : String(e)}`);
|
|
1084
1549
|
}
|
|
1085
1550
|
}
|
|
1086
1551
|
});
|
|
@@ -1088,26 +1553,24 @@ var clearLocalStorageCommand = registerCommand({
|
|
|
1088
1553
|
name: "clear-local-storage",
|
|
1089
1554
|
description: "Clear all localStorage entries",
|
|
1090
1555
|
scope: "page",
|
|
1091
|
-
result:
|
|
1556
|
+
result: z11.object({ cleared: z11.boolean() }),
|
|
1092
1557
|
handler: async (_p, ctx) => {
|
|
1093
1558
|
try {
|
|
1094
1559
|
await ctx.page.evaluate(() => localStorage.clear());
|
|
1095
|
-
return
|
|
1560
|
+
return ok11({ cleared: true });
|
|
1096
1561
|
} catch (e) {
|
|
1097
|
-
return
|
|
1562
|
+
return fail4(`localStorage not accessible on this page: ${e instanceof Error ? e.message : String(e)}`);
|
|
1098
1563
|
}
|
|
1099
1564
|
}
|
|
1100
1565
|
});
|
|
1101
1566
|
|
|
1102
1567
|
// src/commands/screenshot.ts
|
|
1103
|
-
import { z as
|
|
1104
|
-
import { ok as
|
|
1568
|
+
import { z as z12 } from "zod";
|
|
1569
|
+
import { ok as ok12, fail as fail5 } from "@dyyz1993/xcli-core";
|
|
1105
1570
|
import { writeFileSync, mkdirSync } from "fs";
|
|
1106
|
-
import { dirname, join as
|
|
1107
|
-
import { homedir as homedir2 } from "os";
|
|
1108
|
-
var SCREENSHOTS_DIR = join2(homedir2(), ".xbrowser", "screenshots");
|
|
1571
|
+
import { dirname, join as join4 } from "path";
|
|
1109
1572
|
function ensureScreenshotsDir() {
|
|
1110
|
-
mkdirSync(
|
|
1573
|
+
mkdirSync(resolveScreenshotsDir(), { recursive: true });
|
|
1111
1574
|
}
|
|
1112
1575
|
function ensureParentDir(filePath) {
|
|
1113
1576
|
const dir = dirname(filePath);
|
|
@@ -1123,30 +1586,30 @@ function generateScreenshotPath(format) {
|
|
|
1123
1586
|
const timestamp = Date.now();
|
|
1124
1587
|
const random = Math.random().toString(36).slice(2, 8);
|
|
1125
1588
|
const ext = format === "jpeg" ? "jpg" : "png";
|
|
1126
|
-
return
|
|
1589
|
+
return join4(resolveScreenshotsDir(), `screenshot-${timestamp}-${random}.${ext}`);
|
|
1127
1590
|
}
|
|
1128
1591
|
var screenshotCommand = registerCommand({
|
|
1129
1592
|
name: "screenshot",
|
|
1130
|
-
description: "Take a screenshot of the page or element",
|
|
1593
|
+
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>)",
|
|
1131
1594
|
scope: "page",
|
|
1132
1595
|
selectorParams: ["selector"],
|
|
1133
|
-
parameters:
|
|
1134
|
-
selector:
|
|
1135
|
-
type:
|
|
1136
|
-
fullPage:
|
|
1137
|
-
output:
|
|
1138
|
-
base64:
|
|
1596
|
+
parameters: z12.object({
|
|
1597
|
+
selector: z12.string().optional(),
|
|
1598
|
+
type: z12.enum(["png", "jpeg"]).optional(),
|
|
1599
|
+
fullPage: z12.boolean().optional(),
|
|
1600
|
+
output: z12.string().optional(),
|
|
1601
|
+
base64: z12.boolean().optional().describe("Return base64 data instead of file path")
|
|
1139
1602
|
}),
|
|
1140
|
-
result:
|
|
1141
|
-
|
|
1142
|
-
data:
|
|
1143
|
-
format:
|
|
1144
|
-
size:
|
|
1603
|
+
result: z12.union([
|
|
1604
|
+
z12.object({
|
|
1605
|
+
data: z12.string(),
|
|
1606
|
+
format: z12.string(),
|
|
1607
|
+
size: z12.number()
|
|
1145
1608
|
}),
|
|
1146
|
-
|
|
1147
|
-
output:
|
|
1148
|
-
format:
|
|
1149
|
-
size:
|
|
1609
|
+
z12.object({
|
|
1610
|
+
output: z12.string(),
|
|
1611
|
+
format: z12.string(),
|
|
1612
|
+
size: z12.number()
|
|
1150
1613
|
})
|
|
1151
1614
|
]),
|
|
1152
1615
|
handler: async (p, ctx) => {
|
|
@@ -1164,21 +1627,21 @@ var screenshotCommand = registerCommand({
|
|
|
1164
1627
|
if (p.output) {
|
|
1165
1628
|
const dirErr = ensureParentDir(p.output);
|
|
1166
1629
|
if (dirErr) {
|
|
1167
|
-
return
|
|
1630
|
+
return fail5(`Cannot create directory for --output "${p.output}": ${dirErr}`);
|
|
1168
1631
|
}
|
|
1169
1632
|
try {
|
|
1170
1633
|
writeFileSync(p.output, buffer, "binary");
|
|
1171
1634
|
} catch (err) {
|
|
1172
|
-
return
|
|
1635
|
+
return fail5(`Failed to write screenshot to "${p.output}": ${err instanceof Error ? err.message : String(err)}`);
|
|
1173
1636
|
}
|
|
1174
|
-
return
|
|
1637
|
+
return ok12({
|
|
1175
1638
|
output: p.output,
|
|
1176
1639
|
format,
|
|
1177
1640
|
size: buffer.length
|
|
1178
1641
|
});
|
|
1179
1642
|
}
|
|
1180
1643
|
if (p.base64) {
|
|
1181
|
-
return
|
|
1644
|
+
return ok12({
|
|
1182
1645
|
data: buffer.toString("base64"),
|
|
1183
1646
|
format,
|
|
1184
1647
|
size: buffer.length
|
|
@@ -1187,7 +1650,7 @@ var screenshotCommand = registerCommand({
|
|
|
1187
1650
|
ensureScreenshotsDir();
|
|
1188
1651
|
const screenshotPath = generateScreenshotPath(format);
|
|
1189
1652
|
writeFileSync(screenshotPath, buffer, "binary");
|
|
1190
|
-
return
|
|
1653
|
+
return ok12({
|
|
1191
1654
|
output: screenshotPath,
|
|
1192
1655
|
format,
|
|
1193
1656
|
size: buffer.length
|
|
@@ -1196,19 +1659,19 @@ var screenshotCommand = registerCommand({
|
|
|
1196
1659
|
});
|
|
1197
1660
|
|
|
1198
1661
|
// src/commands/structure.ts
|
|
1199
|
-
import { z as
|
|
1200
|
-
import { ok as
|
|
1662
|
+
import { z as z13 } from "zod";
|
|
1663
|
+
import { ok as ok13 } from "@dyyz1993/xcli-core";
|
|
1201
1664
|
var structureCommand = registerCommand({
|
|
1202
1665
|
name: "structure",
|
|
1203
1666
|
description: "Get the DOM structure of the page or an element",
|
|
1204
1667
|
scope: "page",
|
|
1205
1668
|
selectorParams: ["selector"],
|
|
1206
|
-
parameters:
|
|
1207
|
-
selector:
|
|
1208
|
-
depth:
|
|
1669
|
+
parameters: z13.object({
|
|
1670
|
+
selector: z13.string().optional(),
|
|
1671
|
+
depth: z13.number().optional()
|
|
1209
1672
|
}),
|
|
1210
|
-
result:
|
|
1211
|
-
structure:
|
|
1673
|
+
result: z13.object({
|
|
1674
|
+
structure: z13.record(z13.unknown())
|
|
1212
1675
|
}),
|
|
1213
1676
|
handler: async (p, ctx) => {
|
|
1214
1677
|
const structure = await ctx.page.evaluate(
|
|
@@ -1240,30 +1703,30 @@ var structureCommand = registerCommand({
|
|
|
1240
1703
|
},
|
|
1241
1704
|
{ sel: p.selector || "body", maxDepth: p.depth || 5 }
|
|
1242
1705
|
);
|
|
1243
|
-
return
|
|
1706
|
+
return ok13({ structure });
|
|
1244
1707
|
}
|
|
1245
1708
|
});
|
|
1246
1709
|
|
|
1247
1710
|
// src/commands/viewport.ts
|
|
1248
|
-
import { z as
|
|
1249
|
-
import { ok as
|
|
1711
|
+
import { z as z14 } from "zod";
|
|
1712
|
+
import { ok as ok14 } from "@dyyz1993/xcli-core";
|
|
1250
1713
|
var setViewportCommand = registerCommand({
|
|
1251
1714
|
name: "set-viewport",
|
|
1252
1715
|
description: "Set the viewport size and properties",
|
|
1253
1716
|
scope: "browser",
|
|
1254
|
-
parameters:
|
|
1255
|
-
width:
|
|
1256
|
-
height:
|
|
1257
|
-
deviceScaleFactor:
|
|
1258
|
-
isMobile:
|
|
1259
|
-
hasTouch:
|
|
1717
|
+
parameters: z14.object({
|
|
1718
|
+
width: z14.coerce.number(),
|
|
1719
|
+
height: z14.coerce.number(),
|
|
1720
|
+
deviceScaleFactor: z14.coerce.number().optional(),
|
|
1721
|
+
isMobile: z14.boolean().optional(),
|
|
1722
|
+
hasTouch: z14.boolean().optional()
|
|
1260
1723
|
}),
|
|
1261
|
-
result:
|
|
1262
|
-
width:
|
|
1263
|
-
height:
|
|
1264
|
-
deviceScaleFactor:
|
|
1265
|
-
isMobile:
|
|
1266
|
-
hasTouch:
|
|
1724
|
+
result: z14.object({
|
|
1725
|
+
width: z14.number(),
|
|
1726
|
+
height: z14.number(),
|
|
1727
|
+
deviceScaleFactor: z14.number().optional(),
|
|
1728
|
+
isMobile: z14.boolean().optional(),
|
|
1729
|
+
hasTouch: z14.boolean().optional()
|
|
1267
1730
|
}),
|
|
1268
1731
|
handler: async (p, ctx) => {
|
|
1269
1732
|
const viewport = ctx.page.viewportSize();
|
|
@@ -1276,7 +1739,7 @@ var setViewportCommand = registerCommand({
|
|
|
1276
1739
|
...p.isMobile !== void 0 && { isMobile: p.isMobile },
|
|
1277
1740
|
...p.hasTouch !== void 0 && { hasTouch: p.hasTouch }
|
|
1278
1741
|
});
|
|
1279
|
-
return
|
|
1742
|
+
return ok14({
|
|
1280
1743
|
width,
|
|
1281
1744
|
height,
|
|
1282
1745
|
...p.deviceScaleFactor !== void 0 && { deviceScaleFactor: p.deviceScaleFactor },
|
|
@@ -1287,17 +1750,17 @@ var setViewportCommand = registerCommand({
|
|
|
1287
1750
|
});
|
|
1288
1751
|
|
|
1289
1752
|
// src/commands/frame.ts
|
|
1290
|
-
import { z as
|
|
1291
|
-
import { ok as
|
|
1753
|
+
import { z as z15 } from "zod";
|
|
1754
|
+
import { ok as ok15, fail as fail6 } from "@dyyz1993/xcli-core";
|
|
1292
1755
|
var framesCommand = registerCommand({
|
|
1293
1756
|
name: "frames",
|
|
1294
1757
|
description: "List all frames in the current page",
|
|
1295
1758
|
scope: "page",
|
|
1296
|
-
result:
|
|
1297
|
-
frames:
|
|
1298
|
-
index:
|
|
1299
|
-
name:
|
|
1300
|
-
url:
|
|
1759
|
+
result: z15.object({
|
|
1760
|
+
frames: z15.array(z15.object({
|
|
1761
|
+
index: z15.number(),
|
|
1762
|
+
name: z15.string().nullable(),
|
|
1763
|
+
url: z15.string()
|
|
1301
1764
|
}))
|
|
1302
1765
|
}),
|
|
1303
1766
|
handler: async (_p, ctx) => {
|
|
@@ -1308,21 +1771,21 @@ var framesCommand = registerCommand({
|
|
|
1308
1771
|
name: frame.name(),
|
|
1309
1772
|
url: frame.url()
|
|
1310
1773
|
}));
|
|
1311
|
-
return
|
|
1774
|
+
return ok15({ frames: frameList });
|
|
1312
1775
|
}
|
|
1313
1776
|
});
|
|
1314
1777
|
var frameCommand = registerCommand({
|
|
1315
1778
|
name: "frame",
|
|
1316
1779
|
description: "Get frame info by index or name",
|
|
1317
1780
|
scope: "page",
|
|
1318
|
-
parameters:
|
|
1319
|
-
index:
|
|
1320
|
-
name:
|
|
1781
|
+
parameters: z15.object({
|
|
1782
|
+
index: z15.number().int().min(0).optional(),
|
|
1783
|
+
name: z15.string().optional()
|
|
1321
1784
|
}),
|
|
1322
|
-
result:
|
|
1323
|
-
name:
|
|
1324
|
-
url:
|
|
1325
|
-
error:
|
|
1785
|
+
result: z15.object({
|
|
1786
|
+
name: z15.string().nullable(),
|
|
1787
|
+
url: z15.string(),
|
|
1788
|
+
error: z15.string().optional()
|
|
1326
1789
|
}),
|
|
1327
1790
|
handler: async (p, ctx) => {
|
|
1328
1791
|
const discover = ctx.page.discoverFrames;
|
|
@@ -1333,12 +1796,12 @@ var frameCommand = registerCommand({
|
|
|
1333
1796
|
} else if (p.name !== void 0) {
|
|
1334
1797
|
targetFrame = rawFrames.find((f) => f.name() === p.name);
|
|
1335
1798
|
} else {
|
|
1336
|
-
return
|
|
1799
|
+
return fail6("Must provide index or name");
|
|
1337
1800
|
}
|
|
1338
1801
|
if (!targetFrame) {
|
|
1339
|
-
return
|
|
1802
|
+
return fail6("Frame not found");
|
|
1340
1803
|
}
|
|
1341
|
-
return
|
|
1804
|
+
return ok15({
|
|
1342
1805
|
name: targetFrame.name(),
|
|
1343
1806
|
url: targetFrame.url()
|
|
1344
1807
|
});
|
|
@@ -1346,27 +1809,27 @@ var frameCommand = registerCommand({
|
|
|
1346
1809
|
});
|
|
1347
1810
|
|
|
1348
1811
|
// src/commands/ui-debug.ts
|
|
1349
|
-
import { z as
|
|
1350
|
-
import { ok as
|
|
1812
|
+
import { z as z16 } from "zod";
|
|
1813
|
+
import { ok as ok16 } from "@dyyz1993/xcli-core";
|
|
1351
1814
|
var consoleCheckCommand = registerCommand({
|
|
1352
1815
|
name: "console",
|
|
1353
1816
|
description: "Collect and analyze browser console messages (errors, warnings, logs)",
|
|
1354
1817
|
scope: "page",
|
|
1355
|
-
parameters:
|
|
1356
|
-
url:
|
|
1357
|
-
duration:
|
|
1358
|
-
filter:
|
|
1359
|
-
includeStackTraces:
|
|
1818
|
+
parameters: z16.object({
|
|
1819
|
+
url: z16.string().optional().describe("URL to navigate first (optional, uses current page if omitted)"),
|
|
1820
|
+
duration: z16.number().optional().default(5e3).describe("How long to collect messages (ms)"),
|
|
1821
|
+
filter: z16.enum(["all", "error", "warning", "info", "log"]).optional().default("all"),
|
|
1822
|
+
includeStackTraces: z16.boolean().optional().default(true)
|
|
1360
1823
|
}),
|
|
1361
|
-
result:
|
|
1362
|
-
url:
|
|
1363
|
-
duration:
|
|
1364
|
-
total:
|
|
1365
|
-
errors:
|
|
1366
|
-
warnings:
|
|
1367
|
-
messages:
|
|
1368
|
-
summary:
|
|
1369
|
-
passed:
|
|
1824
|
+
result: z16.object({
|
|
1825
|
+
url: z16.string(),
|
|
1826
|
+
duration: z16.number(),
|
|
1827
|
+
total: z16.number(),
|
|
1828
|
+
errors: z16.number(),
|
|
1829
|
+
warnings: z16.number(),
|
|
1830
|
+
messages: z16.array(z16.record(z16.unknown())),
|
|
1831
|
+
summary: z16.string(),
|
|
1832
|
+
passed: z16.boolean()
|
|
1370
1833
|
}),
|
|
1371
1834
|
handler: async (p, ctx) => {
|
|
1372
1835
|
const { page } = ctx;
|
|
@@ -1448,7 +1911,7 @@ ${a.stack || ""}`;
|
|
|
1448
1911
|
const filtered = p.filter === "all" ? messages : messages.filter((m) => m.type === p.filter);
|
|
1449
1912
|
const errorCount = messages.filter((m) => m.type === "error").length;
|
|
1450
1913
|
const warnCount = messages.filter((m) => m.type === "warning").length;
|
|
1451
|
-
return
|
|
1914
|
+
return ok16({
|
|
1452
1915
|
url: page.url(),
|
|
1453
1916
|
duration: p.duration,
|
|
1454
1917
|
total: messages.length,
|
|
@@ -1464,23 +1927,23 @@ var networkCheckCommand = registerCommand({
|
|
|
1464
1927
|
name: "net-debug",
|
|
1465
1928
|
description: "Monitor and analyze network requests \u2014 capture failed requests, slow responses, status codes",
|
|
1466
1929
|
scope: "page",
|
|
1467
|
-
parameters:
|
|
1468
|
-
url:
|
|
1469
|
-
duration:
|
|
1470
|
-
filter:
|
|
1471
|
-
slowThreshold:
|
|
1930
|
+
parameters: z16.object({
|
|
1931
|
+
url: z16.string().optional().describe("URL to navigate first"),
|
|
1932
|
+
duration: z16.number().optional().default(5e3).describe("How long to monitor (ms)"),
|
|
1933
|
+
filter: z16.enum(["all", "failed", "slow", "error", "xhr", "fetch", "document", "stylesheet", "script", "image"]).optional().default("all"),
|
|
1934
|
+
slowThreshold: z16.number().optional().default(3e3).describe('Threshold for "slow" requests (ms)')
|
|
1472
1935
|
}),
|
|
1473
|
-
result:
|
|
1474
|
-
url:
|
|
1475
|
-
duration:
|
|
1476
|
-
totalRequests:
|
|
1477
|
-
failedRequests:
|
|
1478
|
-
slowRequests:
|
|
1479
|
-
errorRequests:
|
|
1480
|
-
totalSizeKB:
|
|
1481
|
-
requests:
|
|
1482
|
-
summary:
|
|
1483
|
-
passed:
|
|
1936
|
+
result: z16.object({
|
|
1937
|
+
url: z16.string(),
|
|
1938
|
+
duration: z16.number(),
|
|
1939
|
+
totalRequests: z16.number(),
|
|
1940
|
+
failedRequests: z16.number(),
|
|
1941
|
+
slowRequests: z16.number(),
|
|
1942
|
+
errorRequests: z16.number(),
|
|
1943
|
+
totalSizeKB: z16.number(),
|
|
1944
|
+
requests: z16.array(z16.record(z16.unknown())),
|
|
1945
|
+
summary: z16.string(),
|
|
1946
|
+
passed: z16.boolean()
|
|
1484
1947
|
}),
|
|
1485
1948
|
handler: async (p, ctx) => {
|
|
1486
1949
|
const { page } = ctx;
|
|
@@ -1562,7 +2025,7 @@ var networkCheckCommand = registerCommand({
|
|
|
1562
2025
|
const slowCount = requests.filter((r) => r.duration >= p.slowThreshold).length;
|
|
1563
2026
|
const errorCount = requests.filter((r) => r.error).length;
|
|
1564
2027
|
const totalSize = requests.reduce((sum, r) => sum + r.size, 0);
|
|
1565
|
-
return
|
|
2028
|
+
return ok16({
|
|
1566
2029
|
url: page.url(),
|
|
1567
2030
|
duration: p.duration,
|
|
1568
2031
|
totalRequests: requests.length,
|
|
@@ -1584,17 +2047,17 @@ var perfCheckCommand = registerCommand({
|
|
|
1584
2047
|
name: "perf",
|
|
1585
2048
|
description: "Audit page performance metrics \u2014 load time, FCP, LCP, CLS, TTFB, resource sizes",
|
|
1586
2049
|
scope: "page",
|
|
1587
|
-
parameters:
|
|
1588
|
-
url:
|
|
1589
|
-
iterations:
|
|
2050
|
+
parameters: z16.object({
|
|
2051
|
+
url: z16.string().optional().describe("URL to navigate (uses current page if omitted)"),
|
|
2052
|
+
iterations: z16.number().optional().default(1).describe("Number of iterations to average")
|
|
1590
2053
|
}),
|
|
1591
|
-
result:
|
|
1592
|
-
url:
|
|
1593
|
-
iterations:
|
|
1594
|
-
metrics:
|
|
1595
|
-
allIterations:
|
|
1596
|
-
passed:
|
|
1597
|
-
summary:
|
|
2054
|
+
result: z16.object({
|
|
2055
|
+
url: z16.string(),
|
|
2056
|
+
iterations: z16.number(),
|
|
2057
|
+
metrics: z16.record(z16.unknown()),
|
|
2058
|
+
allIterations: z16.array(z16.record(z16.unknown())).optional(),
|
|
2059
|
+
passed: z16.boolean(),
|
|
2060
|
+
summary: z16.string()
|
|
1598
2061
|
}),
|
|
1599
2062
|
handler: async (p, ctx) => {
|
|
1600
2063
|
const { page } = ctx;
|
|
@@ -1656,7 +2119,7 @@ var perfCheckCommand = registerCommand({
|
|
|
1656
2119
|
if (resourceStats) avg.resourceStats = resourceStats;
|
|
1657
2120
|
return avg;
|
|
1658
2121
|
})();
|
|
1659
|
-
return
|
|
2122
|
+
return ok16({
|
|
1660
2123
|
url: page.url(),
|
|
1661
2124
|
iterations: p.iterations,
|
|
1662
2125
|
metrics: avgMetrics,
|
|
@@ -1670,23 +2133,23 @@ var healthCheckCommand = registerCommand({
|
|
|
1670
2133
|
name: "health",
|
|
1671
2134
|
description: "Comprehensive page health check \u2014 broken links, missing images, console errors, SEO issues",
|
|
1672
2135
|
scope: "page",
|
|
1673
|
-
parameters:
|
|
1674
|
-
url:
|
|
1675
|
-
checkLinks:
|
|
1676
|
-
checkImages:
|
|
1677
|
-
checkMeta:
|
|
1678
|
-
maxLinks:
|
|
2136
|
+
parameters: z16.object({
|
|
2137
|
+
url: z16.string().optional().describe("URL to check"),
|
|
2138
|
+
checkLinks: z16.boolean().optional().default(true).describe("Check for broken links"),
|
|
2139
|
+
checkImages: z16.boolean().optional().default(true).describe("Check for missing/broken images"),
|
|
2140
|
+
checkMeta: z16.boolean().optional().default(true).describe("Check SEO meta tags"),
|
|
2141
|
+
maxLinks: z16.number().optional().default(50).describe("Max links to check")
|
|
1679
2142
|
}),
|
|
1680
|
-
result:
|
|
1681
|
-
url:
|
|
1682
|
-
title:
|
|
1683
|
-
passed:
|
|
1684
|
-
totalIssues:
|
|
1685
|
-
errors:
|
|
1686
|
-
warnings:
|
|
1687
|
-
info:
|
|
1688
|
-
issues:
|
|
1689
|
-
summary:
|
|
2143
|
+
result: z16.object({
|
|
2144
|
+
url: z16.string(),
|
|
2145
|
+
title: z16.string(),
|
|
2146
|
+
passed: z16.boolean(),
|
|
2147
|
+
totalIssues: z16.number(),
|
|
2148
|
+
errors: z16.number(),
|
|
2149
|
+
warnings: z16.number(),
|
|
2150
|
+
info: z16.number(),
|
|
2151
|
+
issues: z16.array(z16.record(z16.unknown())),
|
|
2152
|
+
summary: z16.string()
|
|
1690
2153
|
}),
|
|
1691
2154
|
handler: async (p, ctx) => {
|
|
1692
2155
|
const { page } = ctx;
|
|
@@ -1773,7 +2236,7 @@ var healthCheckCommand = registerCommand({
|
|
|
1773
2236
|
}, { checkLinks: p.checkLinks, checkImages: p.checkImages, checkMeta: p.checkMeta, maxLinks: p.maxLinks });
|
|
1774
2237
|
const errors = result.issues.filter((i) => i.severity === "error").length;
|
|
1775
2238
|
const warnings = result.issues.filter((i) => i.severity === "warning").length;
|
|
1776
|
-
return
|
|
2239
|
+
return ok16({
|
|
1777
2240
|
url: result.url,
|
|
1778
2241
|
title: result.title,
|
|
1779
2242
|
passed: errors === 0,
|
|
@@ -1788,68 +2251,66 @@ var healthCheckCommand = registerCommand({
|
|
|
1788
2251
|
});
|
|
1789
2252
|
|
|
1790
2253
|
// src/commands/actions.ts
|
|
1791
|
-
import { z as
|
|
1792
|
-
import { ok as
|
|
2254
|
+
import { z as z17 } from "zod";
|
|
2255
|
+
import { ok as ok17 } from "@dyyz1993/xcli-core";
|
|
1793
2256
|
import { writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
1794
|
-
import { join as
|
|
1795
|
-
import { homedir as homedir3 } from "os";
|
|
1796
|
-
var SCREENSHOTS_DIR2 = join3(homedir3(), ".xbrowser", "screenshots");
|
|
2257
|
+
import { join as join5 } from "path";
|
|
1797
2258
|
function ensureScreenshotsDir2() {
|
|
1798
|
-
mkdirSync2(
|
|
2259
|
+
mkdirSync2(resolveScreenshotsDir(), { recursive: true });
|
|
1799
2260
|
}
|
|
1800
2261
|
function generateScreenshotPath2(format) {
|
|
1801
2262
|
const timestamp = Date.now();
|
|
1802
2263
|
const random = Math.random().toString(36).slice(2, 8);
|
|
1803
2264
|
const ext = format === "jpeg" ? "jpg" : "png";
|
|
1804
|
-
return
|
|
2265
|
+
return join5(resolveScreenshotsDir(), `screenshot-${timestamp}-${random}.${ext}`);
|
|
1805
2266
|
}
|
|
1806
|
-
var waitActionSchema =
|
|
1807
|
-
type:
|
|
1808
|
-
milliseconds:
|
|
1809
|
-
selector:
|
|
2267
|
+
var waitActionSchema = z17.object({
|
|
2268
|
+
type: z17.literal("wait"),
|
|
2269
|
+
milliseconds: z17.number().positive().optional(),
|
|
2270
|
+
selector: z17.string().optional()
|
|
1810
2271
|
}).refine(
|
|
1811
2272
|
(d) => (d.milliseconds !== void 0 || d.selector !== void 0) && !(d.milliseconds !== void 0 && d.selector !== void 0),
|
|
1812
2273
|
{ message: "Either 'milliseconds' or 'selector' must be provided, but not both." }
|
|
1813
2274
|
);
|
|
1814
|
-
var clickActionSchema =
|
|
1815
|
-
type:
|
|
1816
|
-
selector:
|
|
1817
|
-
all:
|
|
2275
|
+
var clickActionSchema = z17.object({
|
|
2276
|
+
type: z17.literal("click"),
|
|
2277
|
+
selector: z17.string(),
|
|
2278
|
+
all: z17.boolean().optional()
|
|
1818
2279
|
});
|
|
1819
|
-
var screenshotActionSchema =
|
|
1820
|
-
type:
|
|
1821
|
-
fullPage:
|
|
1822
|
-
quality:
|
|
1823
|
-
viewport:
|
|
1824
|
-
base64:
|
|
2280
|
+
var screenshotActionSchema = z17.object({
|
|
2281
|
+
type: z17.literal("screenshot"),
|
|
2282
|
+
fullPage: z17.boolean().optional(),
|
|
2283
|
+
quality: z17.number().min(1).max(100).optional(),
|
|
2284
|
+
viewport: z17.object({ width: z17.number().int().positive(), height: z17.number().int().positive() }).optional(),
|
|
2285
|
+
base64: z17.boolean().optional().describe("Return base64 data instead of file path")
|
|
1825
2286
|
});
|
|
1826
|
-
var writeActionSchema =
|
|
1827
|
-
type:
|
|
1828
|
-
text:
|
|
2287
|
+
var writeActionSchema = z17.object({
|
|
2288
|
+
type: z17.literal("write"),
|
|
2289
|
+
text: z17.string()
|
|
1829
2290
|
});
|
|
1830
|
-
var pressActionSchema =
|
|
1831
|
-
type:
|
|
1832
|
-
key:
|
|
2291
|
+
var pressActionSchema = z17.object({
|
|
2292
|
+
type: z17.literal("press"),
|
|
2293
|
+
key: z17.string()
|
|
1833
2294
|
});
|
|
1834
|
-
var scrollActionSchema =
|
|
1835
|
-
type:
|
|
1836
|
-
direction:
|
|
1837
|
-
selector:
|
|
2295
|
+
var scrollActionSchema = z17.object({
|
|
2296
|
+
type: z17.literal("scroll"),
|
|
2297
|
+
direction: z17.enum(["up", "down"]).optional(),
|
|
2298
|
+
selector: z17.string().optional()
|
|
1838
2299
|
});
|
|
1839
|
-
var scrapeActionSchema =
|
|
1840
|
-
type:
|
|
2300
|
+
var scrapeActionSchema = z17.object({
|
|
2301
|
+
type: z17.literal("scrape")
|
|
1841
2302
|
});
|
|
1842
|
-
var executeJavascriptActionSchema =
|
|
1843
|
-
type:
|
|
1844
|
-
script:
|
|
2303
|
+
var executeJavascriptActionSchema = z17.object({
|
|
2304
|
+
type: z17.literal("executeJavascript"),
|
|
2305
|
+
script: z17.string()
|
|
1845
2306
|
});
|
|
1846
|
-
var pdfActionSchema =
|
|
1847
|
-
type:
|
|
1848
|
-
landscape:
|
|
1849
|
-
scale:
|
|
1850
|
-
format:
|
|
2307
|
+
var pdfActionSchema = z17.object({
|
|
2308
|
+
type: z17.literal("pdf"),
|
|
2309
|
+
landscape: z17.boolean().optional(),
|
|
2310
|
+
scale: z17.number().optional(),
|
|
2311
|
+
format: z17.enum(["A0", "A1", "A2", "A3", "A4", "A5", "A6", "Letter", "Legal", "Tabloid", "Ledger"]).optional()
|
|
1851
2312
|
});
|
|
1852
|
-
var actionSchema =
|
|
2313
|
+
var actionSchema = z17.union([
|
|
1853
2314
|
waitActionSchema,
|
|
1854
2315
|
clickActionSchema,
|
|
1855
2316
|
screenshotActionSchema,
|
|
@@ -1940,11 +2401,11 @@ var actionsCommand = registerCommand({
|
|
|
1940
2401
|
name: "actions",
|
|
1941
2402
|
description: "Execute a sequence of actions (wait, click, scroll, screenshot, fill, etc.)",
|
|
1942
2403
|
scope: "page",
|
|
1943
|
-
parameters:
|
|
1944
|
-
url:
|
|
1945
|
-
actions:
|
|
1946
|
-
output:
|
|
1947
|
-
timeout:
|
|
2404
|
+
parameters: z17.object({
|
|
2405
|
+
url: z17.string().describe("Starting URL"),
|
|
2406
|
+
actions: z17.array(actionSchema).max(MAX_ACTIONS).describe("Array of actions (max 50)"),
|
|
2407
|
+
output: z17.enum(["text", "json"]).default("json").describe("Output format: text or json"),
|
|
2408
|
+
timeout: z17.number().default(60).describe("Overall timeout in seconds (default: 60)")
|
|
1948
2409
|
}),
|
|
1949
2410
|
handler: async (p, ctx) => {
|
|
1950
2411
|
await ctx.page.goto(p.url, { waitUntil: "domcontentloaded" });
|
|
@@ -1964,14 +2425,14 @@ var actionsCommand = registerCommand({
|
|
|
1964
2425
|
const finalUrl = ctx.page.url();
|
|
1965
2426
|
const timedOut = results.length < p.actions.length;
|
|
1966
2427
|
if (p.output === "text") {
|
|
1967
|
-
return
|
|
2428
|
+
return ok17({
|
|
1968
2429
|
title,
|
|
1969
2430
|
url: finalUrl,
|
|
1970
2431
|
actions: results.map((r) => JSON.stringify(r)).join("\n"),
|
|
1971
2432
|
...timedOut ? { warning: `Timed out after ${p.timeout}s, completed ${results.length}/${p.actions.length} actions` } : {}
|
|
1972
2433
|
});
|
|
1973
2434
|
}
|
|
1974
|
-
return
|
|
2435
|
+
return ok17({
|
|
1975
2436
|
title,
|
|
1976
2437
|
url: finalUrl,
|
|
1977
2438
|
results,
|
|
@@ -1981,8 +2442,8 @@ var actionsCommand = registerCommand({
|
|
|
1981
2442
|
});
|
|
1982
2443
|
|
|
1983
2444
|
// src/commands/scrape.ts
|
|
1984
|
-
import { z as
|
|
1985
|
-
import { ok as
|
|
2445
|
+
import { z as z18 } from "zod";
|
|
2446
|
+
import { ok as ok18, fail as fail7 } from "@dyyz1993/xcli-core";
|
|
1986
2447
|
|
|
1987
2448
|
// src/lib/html-to-markdown.ts
|
|
1988
2449
|
import * as cheerio from "cheerio";
|
|
@@ -2367,15 +2828,15 @@ var scrapeCommand = registerCommand({
|
|
|
2367
2828
|
description: "Scrape a page and convert to Markdown (with JS rendering)",
|
|
2368
2829
|
scope: "project",
|
|
2369
2830
|
selectorParams: ["selector"],
|
|
2370
|
-
parameters:
|
|
2371
|
-
url:
|
|
2372
|
-
selector:
|
|
2373
|
-
timeout:
|
|
2374
|
-
format:
|
|
2375
|
-
onlyMainContent:
|
|
2376
|
-
retries:
|
|
2377
|
-
waitAfterLoad:
|
|
2378
|
-
mode:
|
|
2831
|
+
parameters: z18.object({
|
|
2832
|
+
url: z18.string().optional(),
|
|
2833
|
+
selector: z18.string().optional(),
|
|
2834
|
+
timeout: z18.number().default(3e4),
|
|
2835
|
+
format: z18.enum(["markdown", "html", "text"]).default("markdown"),
|
|
2836
|
+
onlyMainContent: z18.boolean().default(true),
|
|
2837
|
+
retries: z18.number().int().min(0).max(5).optional().default(2).describe("\u91CD\u8BD5\u6B21\u6570\uFF08\u9ED8\u8BA4 2\uFF09"),
|
|
2838
|
+
waitAfterLoad: z18.number().int().optional().default(0).describe("\u9875\u9762\u52A0\u8F7D\u540E\u989D\u5916\u7B49\u5F85\u6BEB\u79D2"),
|
|
2839
|
+
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")
|
|
2379
2840
|
}),
|
|
2380
2841
|
handler: async (p, ctx) => {
|
|
2381
2842
|
const { context, page } = await createEphemeralContext(resolveLaunchOpts(ctx));
|
|
@@ -2383,7 +2844,7 @@ var scrapeCommand = registerCommand({
|
|
|
2383
2844
|
try {
|
|
2384
2845
|
const targetUrl = p.url || page.url();
|
|
2385
2846
|
if (!targetUrl || targetUrl === "about:blank") {
|
|
2386
|
-
return
|
|
2847
|
+
return fail7("URL is required. Provide a URL or connect to a browser (--cdp) with a non-blank page.");
|
|
2387
2848
|
}
|
|
2388
2849
|
let lastError;
|
|
2389
2850
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
@@ -2453,7 +2914,7 @@ var scrapeCommand = registerCommand({
|
|
|
2453
2914
|
} catch {
|
|
2454
2915
|
}
|
|
2455
2916
|
if (p.mode === "smart") {
|
|
2456
|
-
return
|
|
2917
|
+
return ok18(structured);
|
|
2457
2918
|
}
|
|
2458
2919
|
if (p.mode === "compact") {
|
|
2459
2920
|
const compactData = structured.tables.length > 0 ? structured.tables.map((t) => ({
|
|
@@ -2466,9 +2927,9 @@ var scrapeCommand = registerCommand({
|
|
|
2466
2927
|
return compact;
|
|
2467
2928
|
})
|
|
2468
2929
|
})) : structured.mainText?.substring(0, 500);
|
|
2469
|
-
return
|
|
2930
|
+
return ok18({ url: structured.url, title: structured.title, data: compactData });
|
|
2470
2931
|
}
|
|
2471
|
-
return
|
|
2932
|
+
return ok18(structured);
|
|
2472
2933
|
}
|
|
2473
2934
|
let content;
|
|
2474
2935
|
switch (p.format) {
|
|
@@ -2527,7 +2988,7 @@ var scrapeCommand = registerCommand({
|
|
|
2527
2988
|
content = await page.innerText("body");
|
|
2528
2989
|
break;
|
|
2529
2990
|
}
|
|
2530
|
-
return
|
|
2991
|
+
return ok18({ content, title, url: finalUrl });
|
|
2531
2992
|
} catch (err) {
|
|
2532
2993
|
lastError = err instanceof Error ? err : new Error(String(err));
|
|
2533
2994
|
if (attempt < maxAttempts) {
|
|
@@ -2537,20 +2998,20 @@ var scrapeCommand = registerCommand({
|
|
|
2537
2998
|
}
|
|
2538
2999
|
}
|
|
2539
3000
|
}
|
|
2540
|
-
return
|
|
3001
|
+
return fail7(`Scrape failed after ${maxAttempts} attempt(s): ${lastError?.message ?? "unknown error"}`);
|
|
2541
3002
|
} finally {
|
|
2542
3003
|
await closeEphemeralContext(context);
|
|
2543
3004
|
}
|
|
2544
3005
|
},
|
|
2545
|
-
result:
|
|
2546
|
-
url:
|
|
2547
|
-
title:
|
|
3006
|
+
result: z18.object({
|
|
3007
|
+
url: z18.string(),
|
|
3008
|
+
title: z18.string()
|
|
2548
3009
|
}).passthrough()
|
|
2549
3010
|
});
|
|
2550
3011
|
|
|
2551
3012
|
// src/commands/map.ts
|
|
2552
|
-
import { z as
|
|
2553
|
-
import { ok as
|
|
3013
|
+
import { z as z19 } from "zod";
|
|
3014
|
+
import { ok as ok19, fail as fail8 } from "@dyyz1993/xcli-core";
|
|
2554
3015
|
|
|
2555
3016
|
// src/utils/url.ts
|
|
2556
3017
|
var SKIP_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
@@ -2785,21 +3246,21 @@ var mapCommand = registerCommand({
|
|
|
2785
3246
|
name: "map",
|
|
2786
3247
|
description: "Discover all URLs on a website via sitemap and page link extraction",
|
|
2787
3248
|
scope: "project",
|
|
2788
|
-
parameters:
|
|
2789
|
-
url:
|
|
2790
|
-
search:
|
|
2791
|
-
sitemap:
|
|
2792
|
-
includeSubdomains:
|
|
2793
|
-
allowExternalLinks:
|
|
2794
|
-
limit:
|
|
2795
|
-
verbose:
|
|
3249
|
+
parameters: z19.object({
|
|
3250
|
+
url: z19.string().optional(),
|
|
3251
|
+
search: z19.string().optional(),
|
|
3252
|
+
sitemap: z19.enum(["include", "only"]).optional(),
|
|
3253
|
+
includeSubdomains: z19.boolean().optional(),
|
|
3254
|
+
allowExternalLinks: z19.boolean().optional().describe("Include links to external domains"),
|
|
3255
|
+
limit: z19.number().optional(),
|
|
3256
|
+
verbose: z19.boolean().default(false).describe("Show progress feedback")
|
|
2796
3257
|
}),
|
|
2797
3258
|
handler: async (p, ctx) => {
|
|
2798
3259
|
const { context, page } = await createEphemeralContext(resolveLaunchOpts(ctx));
|
|
2799
3260
|
try {
|
|
2800
3261
|
const targetUrl = p.url || page.url();
|
|
2801
3262
|
if (!targetUrl || targetUrl === "about:blank") {
|
|
2802
|
-
return
|
|
3263
|
+
return fail8("URL is required. Provide a URL or connect to a browser (--cdp) with a non-blank page.");
|
|
2803
3264
|
}
|
|
2804
3265
|
const links = await discoverUrls(page, targetUrl, {
|
|
2805
3266
|
sitemap: p.sitemap,
|
|
@@ -2810,7 +3271,7 @@ var mapCommand = registerCommand({
|
|
|
2810
3271
|
verbose: p.verbose
|
|
2811
3272
|
});
|
|
2812
3273
|
const linkObjects = links.map((url) => ({ url }));
|
|
2813
|
-
return
|
|
3274
|
+
return ok19({
|
|
2814
3275
|
links: linkObjects,
|
|
2815
3276
|
success: true
|
|
2816
3277
|
});
|
|
@@ -2818,15 +3279,15 @@ var mapCommand = registerCommand({
|
|
|
2818
3279
|
await closeEphemeralContext(context);
|
|
2819
3280
|
}
|
|
2820
3281
|
},
|
|
2821
|
-
result:
|
|
2822
|
-
links:
|
|
2823
|
-
success:
|
|
3282
|
+
result: z19.object({
|
|
3283
|
+
links: z19.array(z19.object({ url: z19.string() })),
|
|
3284
|
+
success: z19.boolean()
|
|
2824
3285
|
})
|
|
2825
3286
|
});
|
|
2826
3287
|
|
|
2827
3288
|
// src/commands/crawl.ts
|
|
2828
|
-
import { z as
|
|
2829
|
-
import { ok as
|
|
3289
|
+
import { z as z20 } from "zod";
|
|
3290
|
+
import { ok as ok20 } from "@dyyz1993/xcli-core";
|
|
2830
3291
|
function stripHashAnchorQuery(url) {
|
|
2831
3292
|
try {
|
|
2832
3293
|
const parsed = new URL(url);
|
|
@@ -3003,21 +3464,21 @@ var crawlCommand = registerCommand({
|
|
|
3003
3464
|
name: "crawl",
|
|
3004
3465
|
description: "Crawl a website and extract content from all pages",
|
|
3005
3466
|
scope: "project",
|
|
3006
|
-
parameters:
|
|
3007
|
-
url:
|
|
3008
|
-
limit:
|
|
3009
|
-
maxDepth:
|
|
3010
|
-
includePaths:
|
|
3011
|
-
excludePaths:
|
|
3012
|
-
allowSubdomains:
|
|
3013
|
-
allowExternalLinks:
|
|
3014
|
-
allowBackwardCrawling:
|
|
3015
|
-
enableSpa:
|
|
3016
|
-
format:
|
|
3017
|
-
onlyMainContent:
|
|
3018
|
-
concurrency:
|
|
3019
|
-
retries:
|
|
3020
|
-
verbose:
|
|
3467
|
+
parameters: z20.object({
|
|
3468
|
+
url: z20.string(),
|
|
3469
|
+
limit: z20.number().default(10),
|
|
3470
|
+
maxDepth: z20.number().default(3),
|
|
3471
|
+
includePaths: z20.array(z20.string()).optional(),
|
|
3472
|
+
excludePaths: z20.array(z20.string()).optional(),
|
|
3473
|
+
allowSubdomains: z20.boolean().default(false),
|
|
3474
|
+
allowExternalLinks: z20.boolean().default(false),
|
|
3475
|
+
allowBackwardCrawling: z20.boolean().default(false),
|
|
3476
|
+
enableSpa: z20.boolean().default(true).describe("Disable to skip SPA route detection"),
|
|
3477
|
+
format: z20.enum(["markdown", "html"]).default("markdown"),
|
|
3478
|
+
onlyMainContent: z20.boolean().default(true),
|
|
3479
|
+
concurrency: z20.number().default(3),
|
|
3480
|
+
retries: z20.number().default(2),
|
|
3481
|
+
verbose: z20.boolean().default(false)
|
|
3021
3482
|
}),
|
|
3022
3483
|
handler: async (p, ctx) => {
|
|
3023
3484
|
const startUrl = new URL(p.url);
|
|
@@ -3156,7 +3617,7 @@ var crawlCommand = registerCommand({
|
|
|
3156
3617
|
if (errorPages.length > 0) {
|
|
3157
3618
|
response.errors = errorPages;
|
|
3158
3619
|
}
|
|
3159
|
-
return
|
|
3620
|
+
return ok20(response);
|
|
3160
3621
|
} finally {
|
|
3161
3622
|
for (const ctx2 of contexts) {
|
|
3162
3623
|
await ctx2.close().catch(() => {
|
|
@@ -3166,15 +3627,15 @@ var crawlCommand = registerCommand({
|
|
|
3166
3627
|
} catch (err) {
|
|
3167
3628
|
const message = err instanceof Error ? err.message : String(err);
|
|
3168
3629
|
const successPages = results.filter((r) => !isPageError(r));
|
|
3169
|
-
return
|
|
3630
|
+
return ok20({ pages: successPages, total: successPages.length, success: false, error: message });
|
|
3170
3631
|
}
|
|
3171
3632
|
}
|
|
3172
3633
|
});
|
|
3173
3634
|
|
|
3174
3635
|
// src/commands/search.ts
|
|
3175
|
-
import { z as
|
|
3636
|
+
import { z as z21 } from "zod";
|
|
3176
3637
|
import * as cheerio2 from "cheerio";
|
|
3177
|
-
import { ok as
|
|
3638
|
+
import { ok as ok21 } from "@dyyz1993/xcli-core";
|
|
3178
3639
|
function getRecencyParams(recency) {
|
|
3179
3640
|
const now = Math.floor(Date.now() / 1e3);
|
|
3180
3641
|
switch (recency) {
|
|
@@ -3508,16 +3969,16 @@ var searchCommand = registerCommand({
|
|
|
3508
3969
|
name: "search",
|
|
3509
3970
|
description: "Search the web and extract results with engine fallback",
|
|
3510
3971
|
scope: "project",
|
|
3511
|
-
parameters:
|
|
3512
|
-
query:
|
|
3513
|
-
engine:
|
|
3514
|
-
limit:
|
|
3515
|
-
full:
|
|
3516
|
-
format:
|
|
3517
|
-
timeout:
|
|
3518
|
-
recency:
|
|
3519
|
-
fallback:
|
|
3520
|
-
site:
|
|
3972
|
+
parameters: z21.object({
|
|
3973
|
+
query: z21.string(),
|
|
3974
|
+
engine: z21.string().optional(),
|
|
3975
|
+
limit: z21.number().default(10),
|
|
3976
|
+
full: z21.boolean().default(false),
|
|
3977
|
+
format: z21.enum(["markdown", "json", "text"]).default("markdown"),
|
|
3978
|
+
timeout: z21.number().default(15e3),
|
|
3979
|
+
recency: z21.enum(["hour", "day", "week", "month", "year"]).optional().describe("Filter by time: hour/day/week/month/year"),
|
|
3980
|
+
fallback: z21.boolean().default(false).describe("Sequential engine fallback instead of parallel"),
|
|
3981
|
+
site: z21.string().optional().describe("Limit results to a specific site (e.g. github.com, v2ex.com)")
|
|
3521
3982
|
}),
|
|
3522
3983
|
handler: async (p, ctx) => {
|
|
3523
3984
|
const { context } = await createEphemeralContext(resolveLaunchOpts(ctx));
|
|
@@ -3623,7 +4084,7 @@ ${errors.map((e) => ` - ${e.engine}: ${e.error}`).join("\n")}`
|
|
|
3623
4084
|
lines.push(`> ${r.snippet}`);
|
|
3624
4085
|
lines.push("");
|
|
3625
4086
|
}
|
|
3626
|
-
return
|
|
4087
|
+
return ok21({ ...searchResult, content: lines.join("\n") });
|
|
3627
4088
|
}
|
|
3628
4089
|
if (p.format === "text") {
|
|
3629
4090
|
const lines = [`Search: ${searchResult.query} (Engine: ${searchResult.engine}, Total: ${searchResult.total})`, ""];
|
|
@@ -3633,9 +4094,9 @@ ${errors.map((e) => ` - ${e.engine}: ${e.error}`).join("\n")}`
|
|
|
3633
4094
|
lines.push(` ${r.snippet}`);
|
|
3634
4095
|
lines.push("");
|
|
3635
4096
|
}
|
|
3636
|
-
return
|
|
4097
|
+
return ok21({ ...searchResult, content: lines.join("\n") });
|
|
3637
4098
|
}
|
|
3638
|
-
return
|
|
4099
|
+
return ok21(searchResult);
|
|
3639
4100
|
} finally {
|
|
3640
4101
|
await closeEphemeralContext(context);
|
|
3641
4102
|
}
|
|
@@ -3643,8 +4104,8 @@ ${errors.map((e) => ` - ${e.engine}: ${e.error}`).join("\n")}`
|
|
|
3643
4104
|
});
|
|
3644
4105
|
|
|
3645
4106
|
// src/commands/network.ts
|
|
3646
|
-
import { z as
|
|
3647
|
-
import { ok as
|
|
4107
|
+
import { z as z22 } from "zod";
|
|
4108
|
+
import { ok as ok22, fail as fail9 } from "@dyyz1993/xcli-core";
|
|
3648
4109
|
function extractPath2(url) {
|
|
3649
4110
|
try {
|
|
3650
4111
|
const u = new URL(url);
|
|
@@ -3742,19 +4203,19 @@ var networkCommand = registerCommand({
|
|
|
3742
4203
|
name: "network",
|
|
3743
4204
|
description: "Capture and filter network responses from a URL",
|
|
3744
4205
|
scope: "project",
|
|
3745
|
-
parameters:
|
|
3746
|
-
url:
|
|
3747
|
-
filter:
|
|
3748
|
-
match:
|
|
3749
|
-
search:
|
|
3750
|
-
console:
|
|
3751
|
-
timeout:
|
|
3752
|
-
wait:
|
|
3753
|
-
limit:
|
|
3754
|
-
format:
|
|
3755
|
-
ws:
|
|
3756
|
-
listen:
|
|
3757
|
-
duration:
|
|
4206
|
+
parameters: z22.object({
|
|
4207
|
+
url: z22.string(),
|
|
4208
|
+
filter: z22.string().optional(),
|
|
4209
|
+
match: z22.string().optional(),
|
|
4210
|
+
search: z22.string().optional().describe("Search within all captured response bodies"),
|
|
4211
|
+
console: z22.boolean().default(false),
|
|
4212
|
+
timeout: z22.number().default(3e4),
|
|
4213
|
+
wait: z22.number().default(3e3),
|
|
4214
|
+
limit: z22.number().default(50),
|
|
4215
|
+
format: z22.enum(["summary", "json"]).default("summary"),
|
|
4216
|
+
ws: z22.boolean().optional().default(false).describe("Only show WebSocket messages"),
|
|
4217
|
+
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"),
|
|
4218
|
+
duration: z22.number().int().optional().default(15e3).describe("\u76D1\u542C\u6A21\u5F0F\u7B49\u5F85\u65F6\u957F\uFF08\u6BEB\u79D2\uFF0C\u9ED8\u8BA4 15000\uFF09")
|
|
3758
4219
|
}),
|
|
3759
4220
|
handler: async (p, ctx) => {
|
|
3760
4221
|
const startTime = Date.now();
|
|
@@ -3809,7 +4270,7 @@ var networkCommand = registerCommand({
|
|
|
3809
4270
|
};
|
|
3810
4271
|
if (p.listen) {
|
|
3811
4272
|
const page2 = ctx.page;
|
|
3812
|
-
if (!page2) return
|
|
4273
|
+
if (!page2) return fail9("No active page. Use --cdp to connect first.");
|
|
3813
4274
|
const captures = [];
|
|
3814
4275
|
const consoleMessages = [];
|
|
3815
4276
|
const wsCaptures = [];
|
|
@@ -3836,7 +4297,7 @@ var networkCommand = registerCommand({
|
|
|
3836
4297
|
page2.off("response", handler);
|
|
3837
4298
|
const duration = Date.now() - startTime;
|
|
3838
4299
|
if (p.ws && wsCaptures.length > 0) {
|
|
3839
|
-
return
|
|
4300
|
+
return ok22({
|
|
3840
4301
|
url: "[listen mode]",
|
|
3841
4302
|
duration,
|
|
3842
4303
|
total: captures.length,
|
|
@@ -3850,9 +4311,9 @@ var networkCommand = registerCommand({
|
|
|
3850
4311
|
});
|
|
3851
4312
|
}
|
|
3852
4313
|
if (p.format === "json") {
|
|
3853
|
-
return
|
|
4314
|
+
return ok22(buildJsonOutput("[listen mode]", captures, consoleMessages, captures.length, wsCaptures));
|
|
3854
4315
|
}
|
|
3855
|
-
return
|
|
4316
|
+
return ok22(buildSummaryOutput("[listen mode]", duration, captures, consoleMessages, captures.length, wsCaptures));
|
|
3856
4317
|
}
|
|
3857
4318
|
const { context, page } = await createEphemeralContext(resolveLaunchOpts(ctx));
|
|
3858
4319
|
try {
|
|
@@ -3907,7 +4368,7 @@ var networkCommand = registerCommand({
|
|
|
3907
4368
|
}
|
|
3908
4369
|
const duration = Date.now() - startTime;
|
|
3909
4370
|
if (p.ws && wsCaptures.length > 0) {
|
|
3910
|
-
return
|
|
4371
|
+
return ok22({
|
|
3911
4372
|
url: p.url,
|
|
3912
4373
|
duration,
|
|
3913
4374
|
total: captures.length,
|
|
@@ -3921,9 +4382,9 @@ var networkCommand = registerCommand({
|
|
|
3921
4382
|
});
|
|
3922
4383
|
}
|
|
3923
4384
|
if (p.format === "json") {
|
|
3924
|
-
return
|
|
4385
|
+
return ok22(buildJsonOutput(p.url, results, consoleMessages, totalCount, wsCaptures, searchResults));
|
|
3925
4386
|
}
|
|
3926
|
-
return
|
|
4387
|
+
return ok22(buildSummaryOutput(p.url, duration, results, consoleMessages, totalCount, wsCaptures));
|
|
3927
4388
|
} finally {
|
|
3928
4389
|
await closeEphemeralContext(context);
|
|
3929
4390
|
}
|
|
@@ -3931,7 +4392,7 @@ var networkCommand = registerCommand({
|
|
|
3931
4392
|
});
|
|
3932
4393
|
|
|
3933
4394
|
// src/commands/ai-search-engines.ts
|
|
3934
|
-
import { z as
|
|
4395
|
+
import { z as z23 } from "zod";
|
|
3935
4396
|
var ENGINE_CONFIGS = {
|
|
3936
4397
|
deepseek: {
|
|
3937
4398
|
key: "deepseek",
|
|
@@ -4185,11 +4646,11 @@ var ENGINE_CONFIGS = {
|
|
|
4185
4646
|
}
|
|
4186
4647
|
};
|
|
4187
4648
|
var ALL_ENGINE_KEYS = Object.keys(ENGINE_CONFIGS);
|
|
4188
|
-
var ENGINE_KEY_ENUM =
|
|
4649
|
+
var ENGINE_KEY_ENUM = z23.enum(ALL_ENGINE_KEYS);
|
|
4189
4650
|
|
|
4190
4651
|
// src/commands/snapshot.ts
|
|
4191
|
-
import { z as
|
|
4192
|
-
import { ok as
|
|
4652
|
+
import { z as z24 } from "zod";
|
|
4653
|
+
import { ok as ok23, fail as fail10, normalizeTips as normalizeTips3 } from "@dyyz1993/xcli-core";
|
|
4193
4654
|
|
|
4194
4655
|
// src/runtime/ref-store.ts
|
|
4195
4656
|
var sessions = /* @__PURE__ */ new Map();
|
|
@@ -4430,8 +4891,8 @@ async function resolveRefParams(page, params, selectorKeys, cache, sessionId) {
|
|
|
4430
4891
|
}
|
|
4431
4892
|
|
|
4432
4893
|
// src/utils/site-semantics.ts
|
|
4433
|
-
import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync, readFileSync } from "fs";
|
|
4434
|
-
import { join as
|
|
4894
|
+
import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
4895
|
+
import { join as join6, dirname as dirname2 } from "path";
|
|
4435
4896
|
import { homedir as homedir4 } from "os";
|
|
4436
4897
|
import { stringify, parse } from "yaml";
|
|
4437
4898
|
import { execFile } from "child_process";
|
|
@@ -4509,10 +4970,10 @@ function inferAction(role, label) {
|
|
|
4509
4970
|
return {};
|
|
4510
4971
|
}
|
|
4511
4972
|
function getSemanticsDir() {
|
|
4512
|
-
return
|
|
4973
|
+
return join6(homedir4(), ".xbrowser", "site-semantics");
|
|
4513
4974
|
}
|
|
4514
4975
|
function getSemanticsPath(domain) {
|
|
4515
|
-
return
|
|
4976
|
+
return join6(getSemanticsDir(), `${domain}.yaml`);
|
|
4516
4977
|
}
|
|
4517
4978
|
function extractDomain2(url) {
|
|
4518
4979
|
try {
|
|
@@ -4526,9 +4987,9 @@ function saveSemantics(domain, pagePath, url, elements) {
|
|
|
4526
4987
|
const filePath = getSemanticsPath(domain);
|
|
4527
4988
|
const dir = dirname2(filePath);
|
|
4528
4989
|
let site;
|
|
4529
|
-
if (
|
|
4990
|
+
if (existsSync3(filePath)) {
|
|
4530
4991
|
try {
|
|
4531
|
-
site = parse(
|
|
4992
|
+
site = parse(readFileSync3(filePath, "utf-8"));
|
|
4532
4993
|
} catch {
|
|
4533
4994
|
site = { site: domain, pages: {} };
|
|
4534
4995
|
}
|
|
@@ -4548,16 +5009,16 @@ function saveSemantics(domain, pagePath, url, elements) {
|
|
|
4548
5009
|
}
|
|
4549
5010
|
}
|
|
4550
5011
|
site.updated_at = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
4551
|
-
if (!
|
|
5012
|
+
if (!existsSync3(dir)) {
|
|
4552
5013
|
mkdirSync3(dir, { recursive: true });
|
|
4553
5014
|
}
|
|
4554
5015
|
writeFileSync3(filePath, stringify(site, { lineWidth: 0 }));
|
|
4555
5016
|
}
|
|
4556
5017
|
function loadSemantics(domain) {
|
|
4557
5018
|
const filePath = getSemanticsPath(domain);
|
|
4558
|
-
if (!
|
|
5019
|
+
if (!existsSync3(filePath)) return null;
|
|
4559
5020
|
try {
|
|
4560
|
-
return parse(
|
|
5021
|
+
return parse(readFileSync3(filePath, "utf-8"));
|
|
4561
5022
|
} catch {
|
|
4562
5023
|
return null;
|
|
4563
5024
|
}
|
|
@@ -5101,24 +5562,24 @@ var snapshotCommand = registerCommand({
|
|
|
5101
5562
|
description: "Capture a quick page state snapshot \u2014 aria tree, visible text, or DOM summary",
|
|
5102
5563
|
scope: "page",
|
|
5103
5564
|
selectorParams: ["selector"],
|
|
5104
|
-
parameters:
|
|
5105
|
-
type:
|
|
5106
|
-
selector:
|
|
5107
|
-
depth:
|
|
5108
|
-
interactive:
|
|
5109
|
-
interactiveOnly:
|
|
5110
|
-
i:
|
|
5111
|
-
compact:
|
|
5112
|
-
c:
|
|
5113
|
-
selectors:
|
|
5114
|
-
all:
|
|
5565
|
+
parameters: z24.object({
|
|
5566
|
+
type: z24.enum(["aria", "text", "dom", "all"]).default("aria").describe("Snapshot type: aria (accessibility tree), text (visible text), dom (element summary), all (combined)"),
|
|
5567
|
+
selector: z24.string().optional().describe("Scope to a specific element"),
|
|
5568
|
+
depth: z24.number().optional().default(6).describe("Max depth for DOM/aria tree"),
|
|
5569
|
+
interactive: z24.boolean().optional().default(false).describe("Return interactive agent refs only"),
|
|
5570
|
+
interactiveOnly: z24.boolean().optional().default(false).describe("Alias for interactive"),
|
|
5571
|
+
i: z24.boolean().optional().default(false).describe("Short alias for interactive"),
|
|
5572
|
+
compact: z24.boolean().optional().default(false).describe("Include compact xbrowser style snapshot text"),
|
|
5573
|
+
c: z24.boolean().optional().default(false).describe("Short alias for compact"),
|
|
5574
|
+
selectors: z24.boolean().optional().default(false).describe("Include ref to CSS selector map"),
|
|
5575
|
+
all: z24.boolean().optional().default(false).describe("Include hidden interactive targets when using interactive snapshot")
|
|
5115
5576
|
}),
|
|
5116
|
-
result:
|
|
5117
|
-
url:
|
|
5118
|
-
title:
|
|
5119
|
-
aria:
|
|
5120
|
-
text:
|
|
5121
|
-
dom:
|
|
5577
|
+
result: z24.object({
|
|
5578
|
+
url: z24.string(),
|
|
5579
|
+
title: z24.string(),
|
|
5580
|
+
aria: z24.string().optional(),
|
|
5581
|
+
text: z24.string().optional(),
|
|
5582
|
+
dom: z24.record(z24.unknown()).optional()
|
|
5122
5583
|
}),
|
|
5123
5584
|
handler: async (p, ctx) => {
|
|
5124
5585
|
const page = ctx.page;
|
|
@@ -5132,7 +5593,7 @@ var snapshotCommand = registerCommand({
|
|
|
5132
5593
|
if (p.compact || p.c || p.interactive || p.interactiveOnly || p.i) {
|
|
5133
5594
|
observation.compact = formatObservationCompact(observation, { selectors: p.selectors });
|
|
5134
5595
|
}
|
|
5135
|
-
return
|
|
5596
|
+
return ok23(observation, normalizeTips3([
|
|
5136
5597
|
`refs refreshed for ${observation.targets.length} targets; use click @e1 or fill @e2 "text"`
|
|
5137
5598
|
]));
|
|
5138
5599
|
}
|
|
@@ -5140,15 +5601,15 @@ var snapshotCommand = registerCommand({
|
|
|
5140
5601
|
const aria = await captureAriaSnapshot(page, p.selector, p.depth);
|
|
5141
5602
|
const tips = await buildRefTips(page, aria);
|
|
5142
5603
|
persistSemantics(url, aria);
|
|
5143
|
-
return
|
|
5604
|
+
return ok23({ url, title, aria }, normalizeTips3(tips));
|
|
5144
5605
|
}
|
|
5145
5606
|
if (p.type === "text") {
|
|
5146
5607
|
const text = await captureTextSnapshot(page, p.selector);
|
|
5147
|
-
return
|
|
5608
|
+
return ok23({ url, title, text });
|
|
5148
5609
|
}
|
|
5149
5610
|
if (p.type === "dom") {
|
|
5150
5611
|
const dom = await captureDomSnapshot(page, p.selector, p.depth ?? 6);
|
|
5151
|
-
return
|
|
5612
|
+
return ok23({ url, title, dom });
|
|
5152
5613
|
}
|
|
5153
5614
|
if (p.type === "all") {
|
|
5154
5615
|
const [aria, text, dom] = await Promise.all([
|
|
@@ -5158,9 +5619,9 @@ var snapshotCommand = registerCommand({
|
|
|
5158
5619
|
]);
|
|
5159
5620
|
const tips = await buildRefTips(page, aria);
|
|
5160
5621
|
persistSemantics(url, aria);
|
|
5161
|
-
return
|
|
5622
|
+
return ok23({ url, title, aria, text, dom }, normalizeTips3(tips));
|
|
5162
5623
|
}
|
|
5163
|
-
return
|
|
5624
|
+
return fail10(`Unknown snapshot type: ${p.type}`);
|
|
5164
5625
|
}
|
|
5165
5626
|
});
|
|
5166
5627
|
function persistSemantics(url, aria) {
|
|
@@ -5237,22 +5698,22 @@ async function captureDomSnapshot(page, selector, maxDepth) {
|
|
|
5237
5698
|
}
|
|
5238
5699
|
|
|
5239
5700
|
// src/commands/agent.ts
|
|
5240
|
-
import { z as
|
|
5241
|
-
import { ok as
|
|
5701
|
+
import { z as z25 } from "zod";
|
|
5702
|
+
import { ok as ok24, normalizeTips as normalizeTips4 } from "@dyyz1993/xcli-core";
|
|
5242
5703
|
var observeCommand = registerCommand({
|
|
5243
5704
|
name: "observe",
|
|
5244
5705
|
description: "Observe the current page as structured agent targets with session refs",
|
|
5245
5706
|
scope: "page",
|
|
5246
|
-
parameters:
|
|
5247
|
-
includeHidden:
|
|
5248
|
-
limit:
|
|
5249
|
-
compact:
|
|
5250
|
-
selectors:
|
|
5707
|
+
parameters: z25.object({
|
|
5708
|
+
includeHidden: z25.boolean().optional().default(false).describe("Include hidden elements in the target list"),
|
|
5709
|
+
limit: z25.number().int().positive().max(300).optional().default(80).describe("Maximum number of targets to return"),
|
|
5710
|
+
compact: z25.boolean().optional().default(false).describe("Include compact xbrowser style snapshot text"),
|
|
5711
|
+
selectors: z25.boolean().optional().default(false).describe("Include ref to stable CSS selector map")
|
|
5251
5712
|
}),
|
|
5252
|
-
result:
|
|
5253
|
-
targets:
|
|
5254
|
-
selectors:
|
|
5255
|
-
compact:
|
|
5713
|
+
result: z25.object({
|
|
5714
|
+
targets: z25.array(z25.record(z25.unknown())),
|
|
5715
|
+
selectors: z25.record(z25.unknown()).optional(),
|
|
5716
|
+
compact: z25.string().optional()
|
|
5256
5717
|
}).passthrough(),
|
|
5257
5718
|
handler: async (p, ctx) => {
|
|
5258
5719
|
const observation = await observePage(ctx.page, ctx.sessionId, {
|
|
@@ -5261,7 +5722,7 @@ var observeCommand = registerCommand({
|
|
|
5261
5722
|
});
|
|
5262
5723
|
if (p.selectors) observation.selectors = buildSelectorMap(observation);
|
|
5263
5724
|
if (p.compact) observation.compact = formatObservationCompact(observation, { selectors: p.selectors });
|
|
5264
|
-
return
|
|
5725
|
+
return ok24(observation, normalizeTips4([
|
|
5265
5726
|
`refs refreshed for ${observation.targets.length} targets; use act --ref @e1 --action click or click @e1`
|
|
5266
5727
|
]));
|
|
5267
5728
|
}
|
|
@@ -5271,14 +5732,14 @@ var actCommand = registerCommand({
|
|
|
5271
5732
|
description: "Perform an agent action using an observe ref or explicit selector",
|
|
5272
5733
|
scope: "element",
|
|
5273
5734
|
selectorParams: ["selector"],
|
|
5274
|
-
parameters:
|
|
5275
|
-
action:
|
|
5276
|
-
ref:
|
|
5277
|
-
selector:
|
|
5278
|
-
value:
|
|
5279
|
-
key:
|
|
5280
|
-
force:
|
|
5281
|
-
timeout:
|
|
5735
|
+
parameters: z25.object({
|
|
5736
|
+
action: z25.enum(["click", "fill", "type", "press", "select", "check", "hover"]).default("click"),
|
|
5737
|
+
ref: z25.string().optional().describe("Session-scoped ref returned by observe, such as e1"),
|
|
5738
|
+
selector: z25.string().optional().describe("CSS selector fallback when no ref is available"),
|
|
5739
|
+
value: z25.string().optional().describe("Value for fill/type/select"),
|
|
5740
|
+
key: z25.string().optional().describe("Key for press"),
|
|
5741
|
+
force: z25.boolean().optional().default(false).describe("Bypass actionability checks"),
|
|
5742
|
+
timeout: z25.number().optional().default(1e4).describe("Playwright action timeout in milliseconds")
|
|
5282
5743
|
}).refine((p) => !!p.ref || !!p.selector, {
|
|
5283
5744
|
message: "Either ref or selector is required"
|
|
5284
5745
|
}),
|
|
@@ -5292,7 +5753,7 @@ var actCommand = registerCommand({
|
|
|
5292
5753
|
tips: normalizeTips4(result.stale ? ["run observe again to refresh refs"] : [])
|
|
5293
5754
|
};
|
|
5294
5755
|
}
|
|
5295
|
-
return
|
|
5756
|
+
return ok24(result, normalizeTips4(result.stale ? ["ref screen hash changed; run observe if the next action is uncertain"] : []));
|
|
5296
5757
|
}
|
|
5297
5758
|
});
|
|
5298
5759
|
var waitForCommand = registerCommand({
|
|
@@ -5300,16 +5761,16 @@ var waitForCommand = registerCommand({
|
|
|
5300
5761
|
description: "Wait for agent predicates such as text, URL, load state, selector state, or screen hash changes",
|
|
5301
5762
|
scope: "page",
|
|
5302
5763
|
selectorParams: ["selector"],
|
|
5303
|
-
parameters:
|
|
5304
|
-
selector:
|
|
5305
|
-
state:
|
|
5306
|
-
text:
|
|
5307
|
-
url:
|
|
5308
|
-
load:
|
|
5309
|
-
fn:
|
|
5310
|
-
screenHashChanged:
|
|
5311
|
-
timeout:
|
|
5312
|
-
pollInterval:
|
|
5764
|
+
parameters: z25.object({
|
|
5765
|
+
selector: z25.string().optional().describe("CSS selector or observe ref to wait for"),
|
|
5766
|
+
state: z25.enum(["attached", "detached", "visible", "hidden"]).optional().default("visible"),
|
|
5767
|
+
text: z25.string().optional().describe("Visible text to wait for"),
|
|
5768
|
+
url: z25.string().optional().describe("URL substring or glob pattern to wait for"),
|
|
5769
|
+
load: z25.enum(["load", "domcontentloaded", "networkidle"]).optional().describe("Load state to wait for"),
|
|
5770
|
+
fn: z25.string().optional().describe("JavaScript predicate to wait for"),
|
|
5771
|
+
screenHashChanged: z25.string().optional().describe("Previous screenHash from observe"),
|
|
5772
|
+
timeout: z25.number().optional().default(3e4),
|
|
5773
|
+
pollInterval: z25.number().optional().default(200)
|
|
5313
5774
|
}).refine((p) => [p.selector, p.text, p.url, p.load, p.fn, p.screenHashChanged].filter(Boolean).length === 1, {
|
|
5314
5775
|
message: "Provide exactly one wait predicate: selector, text, url, load, fn, or screenHashChanged"
|
|
5315
5776
|
}),
|
|
@@ -5323,30 +5784,30 @@ var waitForCommand = registerCommand({
|
|
|
5323
5784
|
tips: []
|
|
5324
5785
|
};
|
|
5325
5786
|
}
|
|
5326
|
-
return
|
|
5787
|
+
return ok24(result);
|
|
5327
5788
|
}
|
|
5328
5789
|
});
|
|
5329
5790
|
|
|
5330
5791
|
// src/commands/tab.ts
|
|
5331
|
-
import { z as
|
|
5332
|
-
import { ok as
|
|
5333
|
-
var TabParams =
|
|
5334
|
-
subcommand:
|
|
5335
|
-
url:
|
|
5336
|
-
index:
|
|
5792
|
+
import { z as z26 } from "zod";
|
|
5793
|
+
import { ok as ok25, fail as fail11 } from "@dyyz1993/xcli-core";
|
|
5794
|
+
var TabParams = z26.object({
|
|
5795
|
+
subcommand: z26.enum(["list", "new", "close", "switch"]),
|
|
5796
|
+
url: z26.string().optional(),
|
|
5797
|
+
index: z26.number().int().min(0).optional()
|
|
5337
5798
|
});
|
|
5338
5799
|
var tabCommand = registerCommand({
|
|
5339
5800
|
name: "tab",
|
|
5340
5801
|
description: "Manage browser tabs: list, new, close, switch",
|
|
5341
5802
|
scope: "page",
|
|
5342
5803
|
parameters: TabParams,
|
|
5343
|
-
result:
|
|
5344
|
-
success:
|
|
5345
|
-
data:
|
|
5804
|
+
result: z26.object({
|
|
5805
|
+
success: z26.boolean(),
|
|
5806
|
+
data: z26.unknown()
|
|
5346
5807
|
}),
|
|
5347
5808
|
handler: async (p, ctx) => {
|
|
5348
5809
|
if (!ctx.browserContext) {
|
|
5349
|
-
return
|
|
5810
|
+
return fail11("No browser context available. Use --cdp to connect to a browser first.");
|
|
5350
5811
|
}
|
|
5351
5812
|
const pages = ctx.browserContext.pages();
|
|
5352
5813
|
switch (p.subcommand) {
|
|
@@ -5359,7 +5820,7 @@ var tabCommand = registerCommand({
|
|
|
5359
5820
|
case "switch":
|
|
5360
5821
|
return handleSwitch(p, pages, ctx);
|
|
5361
5822
|
default:
|
|
5362
|
-
return
|
|
5823
|
+
return fail11(`Unknown subcommand: ${p.subcommand}`);
|
|
5363
5824
|
}
|
|
5364
5825
|
}
|
|
5365
5826
|
});
|
|
@@ -5374,7 +5835,7 @@ async function handleList(pages, ctx) {
|
|
|
5374
5835
|
if (isActive) activeIndex = i;
|
|
5375
5836
|
tabs.push({ index: i, url, title, active: isActive });
|
|
5376
5837
|
}
|
|
5377
|
-
return
|
|
5838
|
+
return ok25({ tabs, total: tabs.length, activeIndex });
|
|
5378
5839
|
}
|
|
5379
5840
|
async function handleNew(p, _pages, ctx) {
|
|
5380
5841
|
const newPage = await ctx.browserContext.newPage();
|
|
@@ -5402,7 +5863,7 @@ async function handleNew(p, _pages, ctx) {
|
|
|
5402
5863
|
const title = await newPage.title().catch(() => "");
|
|
5403
5864
|
const allPages = ctx.browserContext.pages();
|
|
5404
5865
|
const newIndex = allPages.indexOf(newPage);
|
|
5405
|
-
return
|
|
5866
|
+
return ok25({
|
|
5406
5867
|
index: newIndex >= 0 ? newIndex : allPages.length - 1,
|
|
5407
5868
|
url: newPage.url(),
|
|
5408
5869
|
title,
|
|
@@ -5413,11 +5874,11 @@ async function handleNew(p, _pages, ctx) {
|
|
|
5413
5874
|
async function handleClose(p, ctx) {
|
|
5414
5875
|
const currentPages = ctx.browserContext.pages();
|
|
5415
5876
|
if (currentPages.length <= 1) {
|
|
5416
|
-
return
|
|
5877
|
+
return fail11("Cannot close the last remaining tab");
|
|
5417
5878
|
}
|
|
5418
5879
|
const closeIndex = p.index ?? currentPages.findIndex((pg) => pg === ctx.page);
|
|
5419
5880
|
if (closeIndex < 0 || closeIndex >= currentPages.length) {
|
|
5420
|
-
return
|
|
5881
|
+
return fail11(`Invalid tab index: ${closeIndex}. Valid range: 0-${currentPages.length - 1}`);
|
|
5421
5882
|
}
|
|
5422
5883
|
const pageToClose = currentPages[closeIndex];
|
|
5423
5884
|
const isActivePage = pageToClose === ctx.page;
|
|
@@ -5432,7 +5893,7 @@ async function handleClose(p, ctx) {
|
|
|
5432
5893
|
}
|
|
5433
5894
|
ctx.page = newActivePage;
|
|
5434
5895
|
}
|
|
5435
|
-
return
|
|
5896
|
+
return ok25({
|
|
5436
5897
|
closedIndex: closeIndex,
|
|
5437
5898
|
total: remainingPages.length,
|
|
5438
5899
|
activeIndex: isActivePage ? closeIndex < remainingPages.length ? closeIndex : remainingPages.length - 1 : remainingPages.findIndex((pg) => pg === ctx.page)
|
|
@@ -5440,21 +5901,32 @@ async function handleClose(p, ctx) {
|
|
|
5440
5901
|
}
|
|
5441
5902
|
async function handleSwitch(p, pages, ctx) {
|
|
5442
5903
|
if (p.index === void 0) {
|
|
5443
|
-
return
|
|
5904
|
+
return fail11("Parameter --index is required for switch subcommand");
|
|
5444
5905
|
}
|
|
5445
5906
|
if (p.index < 0 || p.index >= pages.length) {
|
|
5446
|
-
return
|
|
5907
|
+
return fail11(`Invalid tab index: ${p.index}. Valid range: 0-${pages.length - 1}`);
|
|
5447
5908
|
}
|
|
5448
5909
|
const targetPage = pages[p.index];
|
|
5449
5910
|
await targetPage.bringToFront().catch(() => {
|
|
5450
5911
|
});
|
|
5912
|
+
for (let i = 0; i < 5; i++) {
|
|
5913
|
+
const okProbe = await targetPage.evaluate("1").then(() => true, () => false);
|
|
5914
|
+
if (okProbe) break;
|
|
5915
|
+
await new Promise((r) => setTimeout(r, 300));
|
|
5916
|
+
}
|
|
5917
|
+
try {
|
|
5918
|
+
const mouse = targetPage.mouse;
|
|
5919
|
+
if (mouse) await mouse.move(1, 1).catch(() => {
|
|
5920
|
+
});
|
|
5921
|
+
} catch {
|
|
5922
|
+
}
|
|
5451
5923
|
const session = ctx.sessionId ? getSessionById(ctx.sessionId) : void 0;
|
|
5452
5924
|
if (session) {
|
|
5453
5925
|
setActivePage(session, targetPage);
|
|
5454
5926
|
}
|
|
5455
5927
|
ctx.page = targetPage;
|
|
5456
5928
|
const title = await targetPage.title().catch(() => "");
|
|
5457
|
-
return
|
|
5929
|
+
return ok25({
|
|
5458
5930
|
index: p.index,
|
|
5459
5931
|
url: targetPage.url(),
|
|
5460
5932
|
title,
|
|
@@ -5463,9 +5935,9 @@ async function handleSwitch(p, pages, ctx) {
|
|
|
5463
5935
|
}
|
|
5464
5936
|
|
|
5465
5937
|
// src/commands/addinitscript.ts
|
|
5466
|
-
import { z as
|
|
5467
|
-
import { ok as
|
|
5468
|
-
import { readFileSync as
|
|
5938
|
+
import { z as z27 } from "zod";
|
|
5939
|
+
import { ok as ok26 } from "@dyyz1993/xcli-core";
|
|
5940
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
5469
5941
|
|
|
5470
5942
|
// src/chain-parser.ts
|
|
5471
5943
|
import { splitCommand, parseCommandArgs } from "@dyyz1993/xcli-core";
|
|
@@ -5605,14 +6077,14 @@ registerCommandDefinition("tab", ["subcommand"]);
|
|
|
5605
6077
|
registerCommandDefinition("mouse", ["action", "x", "y"]);
|
|
5606
6078
|
|
|
5607
6079
|
// src/commands/addinitscript.ts
|
|
5608
|
-
var InitScriptParams =
|
|
5609
|
-
script:
|
|
5610
|
-
file:
|
|
5611
|
-
stdin:
|
|
5612
|
-
name:
|
|
5613
|
-
list:
|
|
5614
|
-
remove:
|
|
5615
|
-
base64:
|
|
6080
|
+
var InitScriptParams = z27.object({
|
|
6081
|
+
script: z27.string().optional(),
|
|
6082
|
+
file: z27.string().optional(),
|
|
6083
|
+
stdin: z27.boolean().optional(),
|
|
6084
|
+
name: z27.string().optional(),
|
|
6085
|
+
list: z27.boolean().optional(),
|
|
6086
|
+
remove: z27.union([z27.string(), z27.boolean()]).optional(),
|
|
6087
|
+
base64: z27.string().optional()
|
|
5616
6088
|
});
|
|
5617
6089
|
var registeredScripts = /* @__PURE__ */ new Map();
|
|
5618
6090
|
function resolveScriptContent(params) {
|
|
@@ -5620,7 +6092,7 @@ function resolveScriptContent(params) {
|
|
|
5620
6092
|
return Buffer.from(params.base64, "base64").toString("utf-8");
|
|
5621
6093
|
}
|
|
5622
6094
|
if (params.file) {
|
|
5623
|
-
return
|
|
6095
|
+
return readFileSync4(params.file, "utf-8");
|
|
5624
6096
|
}
|
|
5625
6097
|
if (params.stdin) {
|
|
5626
6098
|
return null;
|
|
@@ -5646,14 +6118,14 @@ var addInitScriptCommand = registerCommand({
|
|
|
5646
6118
|
description: "Add an initialization script that runs on every page load",
|
|
5647
6119
|
scope: "page",
|
|
5648
6120
|
parameters: InitScriptParams,
|
|
5649
|
-
result:
|
|
5650
|
-
scripts:
|
|
5651
|
-
removed:
|
|
5652
|
-
existed:
|
|
5653
|
-
error:
|
|
5654
|
-
registered:
|
|
5655
|
-
hint:
|
|
5656
|
-
executedImmediately:
|
|
6121
|
+
result: z27.object({
|
|
6122
|
+
scripts: z27.array(z27.object({ name: z27.string(), size: z27.number(), preview: z27.string() })).optional(),
|
|
6123
|
+
removed: z27.string().optional(),
|
|
6124
|
+
existed: z27.boolean().optional(),
|
|
6125
|
+
error: z27.string().optional(),
|
|
6126
|
+
registered: z27.string().optional(),
|
|
6127
|
+
hint: z27.string().optional(),
|
|
6128
|
+
executedImmediately: z27.boolean().optional()
|
|
5657
6129
|
}).passthrough(),
|
|
5658
6130
|
handler: async (params, ctx) => {
|
|
5659
6131
|
if (params.list) {
|
|
@@ -5662,20 +6134,20 @@ var addInitScriptCommand = registerCommand({
|
|
|
5662
6134
|
size: content2.length,
|
|
5663
6135
|
preview: content2.slice(0, 80)
|
|
5664
6136
|
}));
|
|
5665
|
-
return
|
|
6137
|
+
return ok26({ scripts });
|
|
5666
6138
|
}
|
|
5667
6139
|
if (params.remove && typeof params.remove === "string") {
|
|
5668
6140
|
const existed = registeredScripts.delete(params.remove);
|
|
5669
|
-
return
|
|
6141
|
+
return ok26({ removed: params.remove, existed });
|
|
5670
6142
|
}
|
|
5671
6143
|
const removeTarget = (typeof params.remove === "string" ? params.remove : null) || (params.name && !resolveScriptContent(params) ? params.name : null);
|
|
5672
6144
|
if (removeTarget && !resolveScriptContent(params)) {
|
|
5673
6145
|
const existed = registeredScripts.delete(removeTarget);
|
|
5674
|
-
return
|
|
6146
|
+
return ok26({ removed: removeTarget, existed });
|
|
5675
6147
|
}
|
|
5676
6148
|
let content = params.stdin ? await readStdin() : resolveScriptContent(params);
|
|
5677
6149
|
if (!content) {
|
|
5678
|
-
return
|
|
6150
|
+
return ok26({ error: "No script content provided. Use --script, --file, --stdin, or --base64" });
|
|
5679
6151
|
}
|
|
5680
6152
|
const scriptName = params.name ?? `script-${Date.now()}`;
|
|
5681
6153
|
registeredScripts.set(scriptName, content);
|
|
@@ -5683,45 +6155,45 @@ var addInitScriptCommand = registerCommand({
|
|
|
5683
6155
|
try {
|
|
5684
6156
|
await ctx.page.evaluate(content);
|
|
5685
6157
|
} catch {
|
|
5686
|
-
return
|
|
6158
|
+
return ok26({
|
|
5687
6159
|
registered: scriptName,
|
|
5688
6160
|
hint: "Script registered for future page loads; immediate execution skipped (page may not be ready)"
|
|
5689
6161
|
});
|
|
5690
6162
|
}
|
|
5691
|
-
return
|
|
6163
|
+
return ok26({ registered: scriptName, executedImmediately: true });
|
|
5692
6164
|
}
|
|
5693
6165
|
});
|
|
5694
6166
|
registerCommandDefinition("addinitscript", ["script"]);
|
|
5695
6167
|
|
|
5696
6168
|
// src/commands/find.ts
|
|
5697
|
-
import { z as
|
|
5698
|
-
import { ok as
|
|
5699
|
-
var actionSchema2 =
|
|
6169
|
+
import { z as z28 } from "zod";
|
|
6170
|
+
import { ok as ok27, fail as fail12, normalizeTips as normalizeTips5 } from "@dyyz1993/xcli-core";
|
|
6171
|
+
var actionSchema2 = z28.enum(["click", "fill", "type", "select", "hover", "check"]);
|
|
5700
6172
|
var findCommand = registerCommand({
|
|
5701
6173
|
name: "find",
|
|
5702
6174
|
description: "Find elements by semantic strategy (text/role/label/placeholder/testid) and optionally perform an action",
|
|
5703
6175
|
scope: "page",
|
|
5704
|
-
parameters:
|
|
5705
|
-
strategy:
|
|
5706
|
-
value:
|
|
5707
|
-
name:
|
|
5708
|
-
exact:
|
|
5709
|
-
operation:
|
|
6176
|
+
parameters: z28.object({
|
|
6177
|
+
strategy: z28.enum(["text", "role", "label", "placeholder", "testid", "alt", "title", "first", "last", "nth"]),
|
|
6178
|
+
value: z28.string(),
|
|
6179
|
+
name: z28.string().optional(),
|
|
6180
|
+
exact: z28.boolean().optional().default(false),
|
|
6181
|
+
operation: z28.string().optional().describe('Trailing operation syntax, e.g. click, fill "text", type "text"'),
|
|
5710
6182
|
action: actionSchema2.optional().describe("Action to perform when not using trailing operation syntax"),
|
|
5711
|
-
actionValue:
|
|
5712
|
-
index:
|
|
5713
|
-
click:
|
|
5714
|
-
fill:
|
|
5715
|
-
type:
|
|
5716
|
-
select:
|
|
5717
|
-
hover:
|
|
5718
|
-
check:
|
|
5719
|
-
timeout:
|
|
6183
|
+
actionValue: z28.string().optional().describe("Value for fill/type/select when using action"),
|
|
6184
|
+
index: z28.number().int().optional().describe("Index for nth strategy"),
|
|
6185
|
+
click: z28.boolean().optional().default(false),
|
|
6186
|
+
fill: z28.string().optional(),
|
|
6187
|
+
type: z28.string().optional(),
|
|
6188
|
+
select: z28.string().optional(),
|
|
6189
|
+
hover: z28.boolean().optional().default(false),
|
|
6190
|
+
check: z28.boolean().optional().default(false),
|
|
6191
|
+
timeout: z28.number().optional().default(1e4)
|
|
5720
6192
|
}),
|
|
5721
|
-
result:
|
|
5722
|
-
matched:
|
|
5723
|
-
selector:
|
|
5724
|
-
action:
|
|
6193
|
+
result: z28.object({
|
|
6194
|
+
matched: z28.number(),
|
|
6195
|
+
selector: z28.string(),
|
|
6196
|
+
action: z28.string().optional()
|
|
5725
6197
|
}),
|
|
5726
6198
|
handler: async (p, ctx) => {
|
|
5727
6199
|
const page = ctx.page;
|
|
@@ -5736,7 +6208,7 @@ var findCommand = registerCommand({
|
|
|
5736
6208
|
});
|
|
5737
6209
|
const count = await locator.count();
|
|
5738
6210
|
if (count === 0) {
|
|
5739
|
-
return
|
|
6211
|
+
return fail12(`No element found with ${p.strategy}="${p.value}"`);
|
|
5740
6212
|
}
|
|
5741
6213
|
const tips = [];
|
|
5742
6214
|
const target = selectTarget(locator, p.strategy);
|
|
@@ -5748,15 +6220,15 @@ var findCommand = registerCommand({
|
|
|
5748
6220
|
await target.click({ timeout: p.timeout, force: true });
|
|
5749
6221
|
return okWithTips({ matched: count, selector, action: "click" }, tips);
|
|
5750
6222
|
} else if (actionName === "fill") {
|
|
5751
|
-
if (actionValue === void 0) return
|
|
6223
|
+
if (actionValue === void 0) return fail12("find fill requires a value");
|
|
5752
6224
|
await target.fill(actionValue, { timeout: p.timeout, force: true });
|
|
5753
6225
|
return okWithTips({ matched: count, selector, action: `fill("${actionValue}")` }, tips);
|
|
5754
6226
|
} else if (actionName === "type") {
|
|
5755
|
-
if (actionValue === void 0) return
|
|
6227
|
+
if (actionValue === void 0) return fail12("find type requires a value");
|
|
5756
6228
|
await target.type(actionValue, { delay: 10, timeout: p.timeout });
|
|
5757
6229
|
return okWithTips({ matched: count, selector, action: `type("${actionValue}")` }, tips);
|
|
5758
6230
|
} else if (actionName === "select") {
|
|
5759
|
-
if (actionValue === void 0) return
|
|
6231
|
+
if (actionValue === void 0) return fail12("find select requires a value");
|
|
5760
6232
|
await target.selectOption(actionValue);
|
|
5761
6233
|
return okWithTips({ matched: count, selector, action: `select("${actionValue}")` }, tips);
|
|
5762
6234
|
} else if (actionName === "hover") {
|
|
@@ -5770,7 +6242,7 @@ var findCommand = registerCommand({
|
|
|
5770
6242
|
}
|
|
5771
6243
|
});
|
|
5772
6244
|
function okWithTips(data, tips) {
|
|
5773
|
-
const result =
|
|
6245
|
+
const result = ok27(data);
|
|
5774
6246
|
if (tips.length > 0) result.tips = normalizeTips5(tips);
|
|
5775
6247
|
return result;
|
|
5776
6248
|
}
|
|
@@ -5871,7 +6343,7 @@ function describeSelector(strategy, value, name) {
|
|
|
5871
6343
|
// src/context.ts
|
|
5872
6344
|
function attachDetectAntiBot(ctx) {
|
|
5873
6345
|
ctx.detectAntiBot = async (page, config) => {
|
|
5874
|
-
const { detectAntiBot: detectAntiBot2 } = await import("./anti-bot-
|
|
6346
|
+
const { detectAntiBot: detectAntiBot2 } = await import("./anti-bot-GTTYNEFB.js");
|
|
5875
6347
|
return detectAntiBot2(page, config);
|
|
5876
6348
|
};
|
|
5877
6349
|
}
|
|
@@ -5881,18 +6353,18 @@ import {
|
|
|
5881
6353
|
Core
|
|
5882
6354
|
} from "@dyyz1993/xcli-core";
|
|
5883
6355
|
import { resolve as resolve2 } from "path";
|
|
5884
|
-
import { existsSync as
|
|
6356
|
+
import { existsSync as existsSync6, readdirSync } from "fs";
|
|
5885
6357
|
import { homedir as homedir5 } from "os";
|
|
5886
6358
|
|
|
5887
6359
|
// src/plugin/metadata-parser.ts
|
|
5888
|
-
import { existsSync as
|
|
6360
|
+
import { existsSync as existsSync4 } from "fs";
|
|
5889
6361
|
import { resolve } from "path";
|
|
5890
6362
|
|
|
5891
6363
|
// src/utils/json-file.ts
|
|
5892
|
-
import { readFileSync as
|
|
6364
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
5893
6365
|
function readJsonFile(filePath, defaultValue) {
|
|
5894
6366
|
try {
|
|
5895
|
-
const content =
|
|
6367
|
+
const content = readFileSync5(filePath, "utf-8");
|
|
5896
6368
|
return JSON.parse(content);
|
|
5897
6369
|
} catch {
|
|
5898
6370
|
return defaultValue;
|
|
@@ -5904,7 +6376,7 @@ var PluginMetadataParser = class {
|
|
|
5904
6376
|
static XBROWSER_KEYWORDS = ["xbrowser", "xbrowser-plugin"];
|
|
5905
6377
|
static parseFromPackageJson(pluginPath) {
|
|
5906
6378
|
const packageJsonPath = resolve(pluginPath, "package.json");
|
|
5907
|
-
if (!
|
|
6379
|
+
if (!existsSync4(packageJsonPath)) {
|
|
5908
6380
|
return null;
|
|
5909
6381
|
}
|
|
5910
6382
|
const packageJson = readJsonFile(packageJsonPath, null);
|
|
@@ -5968,20 +6440,20 @@ var PluginMetadataParser = class {
|
|
|
5968
6440
|
};
|
|
5969
6441
|
|
|
5970
6442
|
// src/plugin/ensure-deps.ts
|
|
5971
|
-
import { existsSync as
|
|
5972
|
-
import { join as
|
|
6443
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync4, writeFileSync as writeFileSync5 } from "fs";
|
|
6444
|
+
import { join as join7 } from "path";
|
|
5973
6445
|
import { execSync } from "child_process";
|
|
5974
6446
|
var SHARED_PLUGIN_DEPENDENCIES = {
|
|
5975
6447
|
"zod": "^3.24.0",
|
|
5976
6448
|
"@dyyz1993/xcli-core": "^0.12.1"
|
|
5977
6449
|
};
|
|
5978
6450
|
function ensurePluginDependencies(pluginsDir) {
|
|
5979
|
-
const zodPath =
|
|
5980
|
-
if (
|
|
6451
|
+
const zodPath = join7(pluginsDir, "node_modules", "zod");
|
|
6452
|
+
if (existsSync5(zodPath)) return;
|
|
5981
6453
|
mkdirSync4(pluginsDir, { recursive: true });
|
|
5982
|
-
const pkgPath =
|
|
6454
|
+
const pkgPath = join7(pluginsDir, "package.json");
|
|
5983
6455
|
let pkg = {};
|
|
5984
|
-
if (
|
|
6456
|
+
if (existsSync5(pkgPath)) {
|
|
5985
6457
|
try {
|
|
5986
6458
|
pkg = readJsonFile(pkgPath, {});
|
|
5987
6459
|
} catch {
|
|
@@ -5995,7 +6467,7 @@ function ensurePluginDependencies(pluginsDir) {
|
|
|
5995
6467
|
needsInstall = true;
|
|
5996
6468
|
}
|
|
5997
6469
|
}
|
|
5998
|
-
if (!needsInstall &&
|
|
6470
|
+
if (!needsInstall && existsSync5(join7(pluginsDir, "node_modules"))) return;
|
|
5999
6471
|
pkg.dependencies = existingDeps;
|
|
6000
6472
|
pkg.private = true;
|
|
6001
6473
|
pkg.description = pkg.description || "xbrowser plugins \u2014 shared dependencies";
|
|
@@ -6253,7 +6725,7 @@ var XBrowserPluginLoader = class {
|
|
|
6253
6725
|
const loaded = [];
|
|
6254
6726
|
const seen = /* @__PURE__ */ new Set();
|
|
6255
6727
|
for (const dir of dirs) {
|
|
6256
|
-
if (!
|
|
6728
|
+
if (!existsSync6(dir)) continue;
|
|
6257
6729
|
const entries = readdirSync(dir, { withFileTypes: true });
|
|
6258
6730
|
for (const entry of entries) {
|
|
6259
6731
|
if (!entry.isDirectory()) continue;
|
|
@@ -6261,12 +6733,12 @@ var XBrowserPluginLoader = class {
|
|
|
6261
6733
|
seen.add(entry.name);
|
|
6262
6734
|
const pluginDir = resolve2(dir, entry.name);
|
|
6263
6735
|
let indexPath = resolve2(pluginDir, "index.js");
|
|
6264
|
-
if (!
|
|
6736
|
+
if (!existsSync6(indexPath)) {
|
|
6265
6737
|
indexPath = resolve2(pluginDir, "index.ts");
|
|
6266
6738
|
}
|
|
6267
|
-
if (!
|
|
6739
|
+
if (!existsSync6(indexPath)) continue;
|
|
6268
6740
|
try {
|
|
6269
|
-
if (!
|
|
6741
|
+
if (!existsSync6(resolve2(pluginDir, "package.json"))) {
|
|
6270
6742
|
console.warn(`\u26A0\uFE0F Plugin "${entry.name}" has no package.json. Use "xbrowser create ${entry.name} --template static" for proper structure.`);
|
|
6271
6743
|
} else {
|
|
6272
6744
|
const metadata = PluginMetadataParser.parseFromPackageJson(pluginDir);
|
|
@@ -6923,7 +7395,7 @@ async function loadHooks() {
|
|
|
6923
7395
|
|
|
6924
7396
|
// src/executor.ts
|
|
6925
7397
|
import { homedir as homedir6 } from "os";
|
|
6926
|
-
import { join as
|
|
7398
|
+
import { join as join8 } from "path";
|
|
6927
7399
|
function levenshtein(a, b) {
|
|
6928
7400
|
const m = a.length, n = b.length;
|
|
6929
7401
|
const dp = Array(m + 1).fill(null).map(() => Array(n + 1).fill(0));
|
|
@@ -6938,7 +7410,7 @@ function levenshtein(a, b) {
|
|
|
6938
7410
|
}
|
|
6939
7411
|
var NAVIGATION_COMMANDS = /* @__PURE__ */ new Set(["goto", "back", "forward", "refresh"]);
|
|
6940
7412
|
var snapshotHintShown = /* @__PURE__ */ new WeakSet();
|
|
6941
|
-
var CONFIG_DIR2 =
|
|
7413
|
+
var CONFIG_DIR2 = join8(homedir6(), ".xbrowser");
|
|
6942
7414
|
var storageCache = /* @__PURE__ */ new Map();
|
|
6943
7415
|
function getPluginStorage(pluginName) {
|
|
6944
7416
|
if (!storageCache.has(pluginName)) {
|
|
@@ -6950,7 +7422,7 @@ var archiveInitialized = false;
|
|
|
6950
7422
|
function ensureArchiveInit() {
|
|
6951
7423
|
if (!archiveInitialized) {
|
|
6952
7424
|
try {
|
|
6953
|
-
configureArchiveStore({ archiveDir:
|
|
7425
|
+
configureArchiveStore({ archiveDir: join8(homedir6(), ".xbrowser", "archives") });
|
|
6954
7426
|
} catch {
|
|
6955
7427
|
}
|
|
6956
7428
|
archiveInitialized = true;
|
|
@@ -6974,7 +7446,7 @@ async function guardCheck(commandName) {
|
|
|
6974
7446
|
}
|
|
6975
7447
|
}
|
|
6976
7448
|
function errorResult(message) {
|
|
6977
|
-
return { ...
|
|
7449
|
+
return { ...fail13(message), duration: 0 };
|
|
6978
7450
|
}
|
|
6979
7451
|
function tipsToMessages(tips) {
|
|
6980
7452
|
if (!tips || tips.length === 0) return [];
|
|
@@ -7051,7 +7523,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
7051
7523
|
}
|
|
7052
7524
|
let targetPageOverride = null;
|
|
7053
7525
|
if (_target && extraOpts?.cdpEndpoint) {
|
|
7054
|
-
const { findTargetPage } = await import("./browser-
|
|
7526
|
+
const { findTargetPage } = await import("./browser-ILXTEWH4.js");
|
|
7055
7527
|
targetPageOverride = await findTargetPage(extraOpts.cdpEndpoint, _target);
|
|
7056
7528
|
if (!targetPageOverride) {
|
|
7057
7529
|
return errorResult(`Target "${_target}" not found. Use 'xbrowser targets --cdp ${extraOpts.cdpEndpoint}' to list available pages.`);
|
|
@@ -7068,8 +7540,15 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
7068
7540
|
params = result.data;
|
|
7069
7541
|
}
|
|
7070
7542
|
if (command.scope !== "cli" && !process.env.XBROWSER_DAEMON_WORKER) {
|
|
7071
|
-
const { forwardExec } = await import("./daemon-client-
|
|
7072
|
-
const
|
|
7543
|
+
const { forwardExec } = await import("./daemon-client-HEGAXWGK.js");
|
|
7544
|
+
const longRunning = /^(vision-task|crawl|scrape|record|replay)$/.test(commandName);
|
|
7545
|
+
const result = await forwardExec(
|
|
7546
|
+
commandName,
|
|
7547
|
+
params,
|
|
7548
|
+
sessionName,
|
|
7549
|
+
extraOpts?.cdpEndpoint,
|
|
7550
|
+
longRunning ? 20 * 6e4 : void 0
|
|
7551
|
+
);
|
|
7073
7552
|
if (result) return result;
|
|
7074
7553
|
}
|
|
7075
7554
|
let session;
|
|
@@ -7237,7 +7716,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
7237
7716
|
timestamp: start
|
|
7238
7717
|
});
|
|
7239
7718
|
if (isSuccess) {
|
|
7240
|
-
return { ...
|
|
7719
|
+
return { ...ok28(raw.data, merged.length > 0 ? merged : raw.tips), duration, ...hookOutputs ? { hookOutputs } : {} };
|
|
7241
7720
|
}
|
|
7242
7721
|
return { success: false, data: raw.data, message: raw.message, tips: mergedOrRaw, duration, ...hookOutputs ? { hookOutputs } : {} };
|
|
7243
7722
|
}
|
|
@@ -7251,14 +7730,14 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
7251
7730
|
duration,
|
|
7252
7731
|
timestamp: start
|
|
7253
7732
|
});
|
|
7254
|
-
return { ...
|
|
7733
|
+
return { ...ok28(raw, smartTipNormalized), duration, ...hookOutputs ? { hookOutputs } : {} };
|
|
7255
7734
|
} catch (err) {
|
|
7256
7735
|
const end = Date.now();
|
|
7257
7736
|
const duration = end - start;
|
|
7258
7737
|
const errorMessage = errMsg(err);
|
|
7259
7738
|
if (session?.page && process.env.XBROWSER_RECOVERY && !extraOpts?._recoveryAttempted) {
|
|
7260
7739
|
try {
|
|
7261
|
-
const { attemptRecovery } = await import("./recovery-
|
|
7740
|
+
const { attemptRecovery } = await import("./recovery-FTZGV6VY.js");
|
|
7262
7741
|
const recovery = await attemptRecovery(
|
|
7263
7742
|
session.page,
|
|
7264
7743
|
sessionName,
|
|
@@ -7296,7 +7775,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
7296
7775
|
duration,
|
|
7297
7776
|
timestamp: start
|
|
7298
7777
|
});
|
|
7299
|
-
return { ...
|
|
7778
|
+
return { ...fail13(errorMessage), duration };
|
|
7300
7779
|
} finally {
|
|
7301
7780
|
}
|
|
7302
7781
|
}
|
|
@@ -7337,7 +7816,7 @@ async function executeChain(input, options) {
|
|
|
7337
7816
|
results.push({
|
|
7338
7817
|
command: cmdName,
|
|
7339
7818
|
raw: cmdStr,
|
|
7340
|
-
...
|
|
7819
|
+
...fail13(`Plugin "${cmdName}" requires a sub-command`),
|
|
7341
7820
|
duration: 0
|
|
7342
7821
|
});
|
|
7343
7822
|
if (type === "and") {
|
|
@@ -7356,7 +7835,7 @@ async function executeChain(input, options) {
|
|
|
7356
7835
|
results.push({
|
|
7357
7836
|
command: cmdName,
|
|
7358
7837
|
raw: cmdStr,
|
|
7359
|
-
...
|
|
7838
|
+
...fail13(`Unknown command "${subCommand}" for plugin "${cmdName}"`),
|
|
7360
7839
|
duration: 0
|
|
7361
7840
|
});
|
|
7362
7841
|
if (type === "and") {
|
|
@@ -7460,7 +7939,7 @@ async function executeChain(input, options) {
|
|
|
7460
7939
|
results.push({
|
|
7461
7940
|
command: `${cmdName} ${subCommand}`,
|
|
7462
7941
|
raw: cmdStr,
|
|
7463
|
-
...
|
|
7942
|
+
...ok28(data),
|
|
7464
7943
|
duration: duration2,
|
|
7465
7944
|
...hookOutputs ? { hookOutputs } : {}
|
|
7466
7945
|
});
|
|
@@ -7488,7 +7967,7 @@ async function executeChain(input, options) {
|
|
|
7488
7967
|
results.push({
|
|
7489
7968
|
command: `${cmdName} ${subCommand}`,
|
|
7490
7969
|
raw: cmdStr,
|
|
7491
|
-
...
|
|
7970
|
+
...fail13(errorMessage),
|
|
7492
7971
|
duration: duration2
|
|
7493
7972
|
});
|
|
7494
7973
|
if (type === "and") {
|
|
@@ -7769,25 +8248,25 @@ Run "xbrowser config list" to see current keys.`
|
|
|
7769
8248
|
|
|
7770
8249
|
// src/plugin/installer.ts
|
|
7771
8250
|
import {
|
|
7772
|
-
existsSync as
|
|
8251
|
+
existsSync as existsSync12,
|
|
7773
8252
|
readdirSync as readdirSync2,
|
|
7774
8253
|
mkdirSync as mkdirSync8,
|
|
7775
8254
|
rmSync as rmSync6,
|
|
7776
8255
|
copyFileSync,
|
|
7777
8256
|
cpSync as cpSync6,
|
|
7778
|
-
readFileSync as
|
|
8257
|
+
readFileSync as readFileSync10,
|
|
7779
8258
|
writeFileSync as writeFileSync10
|
|
7780
8259
|
} from "fs";
|
|
7781
8260
|
import { resolve as resolve8, basename as basename2, dirname as dirname4 } from "path";
|
|
7782
8261
|
import { homedir as homedir7 } from "os";
|
|
7783
8262
|
|
|
7784
8263
|
// src/plugin/install-sources/local.ts
|
|
7785
|
-
import { existsSync as
|
|
8264
|
+
import { existsSync as existsSync7, cpSync, rmSync } from "fs";
|
|
7786
8265
|
import { resolve as resolve3 } from "path";
|
|
7787
8266
|
import { verifyPlugin, safeCleanup } from "@dyyz1993/xcli-core";
|
|
7788
8267
|
async function installFromLocal(source, name, targetDir) {
|
|
7789
8268
|
const srcPath = resolve3(source);
|
|
7790
|
-
if (!
|
|
8269
|
+
if (!existsSync7(srcPath)) {
|
|
7791
8270
|
throw new Error(`Local path does not exist: ${srcPath}`);
|
|
7792
8271
|
}
|
|
7793
8272
|
const tmpTarget = `${targetDir}-tmp-${Date.now()}`;
|
|
@@ -7800,7 +8279,7 @@ async function installFromLocal(source, name, targetDir) {
|
|
|
7800
8279
|
safeCleanup(tmpTarget);
|
|
7801
8280
|
throw new Error(`Invalid plugin: ${verify.error}`);
|
|
7802
8281
|
}
|
|
7803
|
-
if (
|
|
8282
|
+
if (existsSync7(targetDir)) {
|
|
7804
8283
|
rmSync(targetDir, { recursive: true, force: true });
|
|
7805
8284
|
}
|
|
7806
8285
|
cpSync(tmpTarget, targetDir, { recursive: true, force: true });
|
|
@@ -7820,8 +8299,8 @@ async function installFromLocal(source, name, targetDir) {
|
|
|
7820
8299
|
}
|
|
7821
8300
|
|
|
7822
8301
|
// src/plugin/install-sources/npm.ts
|
|
7823
|
-
import { existsSync as
|
|
7824
|
-
import { resolve as resolve4, join as
|
|
8302
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync6, rmSync as rmSync2, cpSync as cpSync2 } from "fs";
|
|
8303
|
+
import { resolve as resolve4, join as join9 } from "path";
|
|
7825
8304
|
import { tmpdir } from "os";
|
|
7826
8305
|
import {
|
|
7827
8306
|
downloadToFile,
|
|
@@ -7894,13 +8373,13 @@ async function installFromNpm(packageName, name, targetDir) {
|
|
|
7894
8373
|
throw new Error(`No tarball URL for ${packageName}@${latestVersion}`);
|
|
7895
8374
|
}
|
|
7896
8375
|
const tarballUrl = versionMeta.dist.tarball;
|
|
7897
|
-
const tmpDir =
|
|
8376
|
+
const tmpDir = join9(tmpdir(), `xbrowser-npm-${Date.now()}`);
|
|
7898
8377
|
mkdirSync5(tmpDir, { recursive: true });
|
|
7899
8378
|
let warnings = [];
|
|
7900
8379
|
try {
|
|
7901
|
-
const tarballPath =
|
|
8380
|
+
const tarballPath = join9(tmpDir, `${name}.tgz`);
|
|
7902
8381
|
await downloadToFile(tarballUrl, tarballPath);
|
|
7903
|
-
const extractDir =
|
|
8382
|
+
const extractDir = join9(tmpDir, "extracted");
|
|
7904
8383
|
extractTarGz(tarballPath, extractDir);
|
|
7905
8384
|
flattenPackageRoot(extractDir);
|
|
7906
8385
|
const verify = verifyPlugin2(extractDir, { metadataField: "xbrowser" });
|
|
@@ -7908,13 +8387,13 @@ async function installFromNpm(packageName, name, targetDir) {
|
|
|
7908
8387
|
if (!verify.valid) {
|
|
7909
8388
|
throw new Error(`Invalid npm plugin: ${verify.error}`);
|
|
7910
8389
|
}
|
|
7911
|
-
if (
|
|
8390
|
+
if (existsSync8(targetDir)) {
|
|
7912
8391
|
rmSync2(targetDir, { recursive: true, force: true });
|
|
7913
8392
|
}
|
|
7914
8393
|
cpSync2(extractDir, targetDir, { recursive: true, force: true });
|
|
7915
8394
|
const pkgPath = resolve4(targetDir, "package.json");
|
|
7916
|
-
if (
|
|
7917
|
-
const pkg = JSON.parse(
|
|
8395
|
+
if (existsSync8(pkgPath)) {
|
|
8396
|
+
const pkg = JSON.parse(readFileSync6(pkgPath, "utf-8"));
|
|
7918
8397
|
if (!pkg._npmSource) {
|
|
7919
8398
|
pkg._npmSource = { name: packageName, version: latestVersion };
|
|
7920
8399
|
writeFileSync6(pkgPath, JSON.stringify(pkg, null, 2));
|
|
@@ -7934,13 +8413,13 @@ async function installFromNpm(packageName, name, targetDir) {
|
|
|
7934
8413
|
}
|
|
7935
8414
|
|
|
7936
8415
|
// src/plugin/install-sources/git.ts
|
|
7937
|
-
import { existsSync as
|
|
7938
|
-
import { resolve as resolve5, join as
|
|
8416
|
+
import { existsSync as existsSync9, readFileSync as readFileSync7, writeFileSync as writeFileSync7, rmSync as rmSync3, cpSync as cpSync3 } from "fs";
|
|
8417
|
+
import { resolve as resolve5, join as join10 } from "path";
|
|
7939
8418
|
import { tmpdir as tmpdir2 } from "os";
|
|
7940
8419
|
import { execSync as execSync2 } from "child_process";
|
|
7941
8420
|
import { verifyPlugin as verifyPlugin3, safeCleanup as safeCleanup3 } from "@dyyz1993/xcli-core";
|
|
7942
8421
|
async function installFromGit(gitUrl, name, targetDir) {
|
|
7943
|
-
const tmpDir =
|
|
8422
|
+
const tmpDir = join10(tmpdir2(), `xbrowser-git-${Date.now()}`);
|
|
7944
8423
|
let warnings = [];
|
|
7945
8424
|
try {
|
|
7946
8425
|
execSync2(`git clone --depth 1 "${gitUrl}" "${tmpDir}"`, { stdio: "pipe" });
|
|
@@ -7949,14 +8428,14 @@ async function installFromGit(gitUrl, name, targetDir) {
|
|
|
7949
8428
|
if (!verify.valid) {
|
|
7950
8429
|
throw new Error(`Invalid git plugin: ${verify.error}`);
|
|
7951
8430
|
}
|
|
7952
|
-
if (
|
|
8431
|
+
if (existsSync9(targetDir)) {
|
|
7953
8432
|
rmSync3(targetDir, { recursive: true, force: true });
|
|
7954
8433
|
}
|
|
7955
8434
|
cpSync3(tmpDir, targetDir, { recursive: true, force: true });
|
|
7956
8435
|
rmSync3(resolve5(targetDir, ".git"), { recursive: true, force: true });
|
|
7957
8436
|
const pkgPath = resolve5(targetDir, "package.json");
|
|
7958
|
-
if (
|
|
7959
|
-
const pkg = JSON.parse(
|
|
8437
|
+
if (existsSync9(pkgPath)) {
|
|
8438
|
+
const pkg = JSON.parse(readFileSync7(pkgPath, "utf-8"));
|
|
7960
8439
|
if (!pkg._gitSource) {
|
|
7961
8440
|
pkg._gitSource = { url: gitUrl };
|
|
7962
8441
|
writeFileSync7(pkgPath, JSON.stringify(pkg, null, 2));
|
|
@@ -7976,8 +8455,8 @@ async function installFromGit(gitUrl, name, targetDir) {
|
|
|
7976
8455
|
}
|
|
7977
8456
|
|
|
7978
8457
|
// src/plugin/install-sources/url.ts
|
|
7979
|
-
import { existsSync as
|
|
7980
|
-
import { resolve as resolve6, join as
|
|
8458
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync6, readFileSync as readFileSync8, writeFileSync as writeFileSync8, rmSync as rmSync4, cpSync as cpSync4 } from "fs";
|
|
8459
|
+
import { resolve as resolve6, join as join11, basename } from "path";
|
|
7981
8460
|
import { tmpdir as tmpdir3 } from "os";
|
|
7982
8461
|
import {
|
|
7983
8462
|
downloadToFile as downloadToFile2,
|
|
@@ -7987,14 +8466,14 @@ import {
|
|
|
7987
8466
|
safeCleanup as safeCleanup4
|
|
7988
8467
|
} from "@dyyz1993/xcli-core";
|
|
7989
8468
|
async function installFromUrl(url, name, targetDir) {
|
|
7990
|
-
const tmpDir =
|
|
8469
|
+
const tmpDir = join11(tmpdir3(), `xbrowser-url-${Date.now()}`);
|
|
7991
8470
|
mkdirSync6(tmpDir, { recursive: true });
|
|
7992
8471
|
let warnings = [];
|
|
7993
8472
|
try {
|
|
7994
8473
|
const fileName = basename(new URL(url).pathname) || "plugin.tar.gz";
|
|
7995
|
-
const tarballPath =
|
|
8474
|
+
const tarballPath = join11(tmpDir, fileName);
|
|
7996
8475
|
await downloadToFile2(url, tarballPath);
|
|
7997
|
-
const extractDir =
|
|
8476
|
+
const extractDir = join11(tmpDir, "extracted");
|
|
7998
8477
|
extractTarGz2(tarballPath, extractDir);
|
|
7999
8478
|
flattenPackageRoot2(extractDir);
|
|
8000
8479
|
const verify = verifyPlugin4(extractDir, { metadataField: "xbrowser" });
|
|
@@ -8002,13 +8481,13 @@ async function installFromUrl(url, name, targetDir) {
|
|
|
8002
8481
|
if (!verify.valid) {
|
|
8003
8482
|
throw new Error(`Invalid plugin from URL: ${verify.error}`);
|
|
8004
8483
|
}
|
|
8005
|
-
if (
|
|
8484
|
+
if (existsSync10(targetDir)) {
|
|
8006
8485
|
rmSync4(targetDir, { recursive: true, force: true });
|
|
8007
8486
|
}
|
|
8008
8487
|
cpSync4(extractDir, targetDir, { recursive: true, force: true });
|
|
8009
8488
|
const pkgPath = resolve6(targetDir, "package.json");
|
|
8010
|
-
if (
|
|
8011
|
-
const pkg = JSON.parse(
|
|
8489
|
+
if (existsSync10(pkgPath)) {
|
|
8490
|
+
const pkg = JSON.parse(readFileSync8(pkgPath, "utf-8"));
|
|
8012
8491
|
if (!pkg._urlSource) {
|
|
8013
8492
|
pkg._urlSource = { url };
|
|
8014
8493
|
writeFileSync8(pkgPath, JSON.stringify(pkg, null, 2));
|
|
@@ -8029,14 +8508,14 @@ async function installFromUrl(url, name, targetDir) {
|
|
|
8029
8508
|
|
|
8030
8509
|
// src/plugin/install-sources/marketplace.ts
|
|
8031
8510
|
import {
|
|
8032
|
-
existsSync as
|
|
8511
|
+
existsSync as existsSync11,
|
|
8033
8512
|
mkdirSync as mkdirSync7,
|
|
8034
8513
|
writeFileSync as writeFileSync9,
|
|
8035
|
-
readFileSync as
|
|
8514
|
+
readFileSync as readFileSync9,
|
|
8036
8515
|
rmSync as rmSync5,
|
|
8037
8516
|
cpSync as cpSync5
|
|
8038
8517
|
} from "fs";
|
|
8039
|
-
import { resolve as resolve7, join as
|
|
8518
|
+
import { resolve as resolve7, join as join12, dirname as dirname3 } from "path";
|
|
8040
8519
|
import { tmpdir as tmpdir4 } from "os";
|
|
8041
8520
|
import { gunzipSync } from "zlib";
|
|
8042
8521
|
import {
|
|
@@ -8061,11 +8540,11 @@ async function installFromMarketplace(pluginsDir, slug, options) {
|
|
|
8061
8540
|
const plugin = detailData.data;
|
|
8062
8541
|
const name = options?.name || String(plugin.slug || slug);
|
|
8063
8542
|
const targetDir = resolve7(pluginsDir, name);
|
|
8064
|
-
if (
|
|
8543
|
+
if (existsSync11(targetDir) && !options?.force) {
|
|
8065
8544
|
throw new Error(`Plugin "${name}" already exists. Use --force to overwrite.`);
|
|
8066
8545
|
}
|
|
8067
8546
|
mkdirSync7(targetDir, { recursive: true });
|
|
8068
|
-
const tmpDir =
|
|
8547
|
+
const tmpDir = join12(tmpdir4(), `xbrowser-marketplace-${Date.now()}`);
|
|
8069
8548
|
mkdirSync7(tmpDir, { recursive: true });
|
|
8070
8549
|
const realSlug = String(plugin.slug || slug);
|
|
8071
8550
|
try {
|
|
@@ -8096,7 +8575,7 @@ function isManifestArray(data) {
|
|
|
8096
8575
|
return Array.isArray(data) && data.length > 0 && typeof data[0].path === "string" && typeof data[0].content === "string";
|
|
8097
8576
|
}
|
|
8098
8577
|
function extractManifestToDir(manifest, targetDir) {
|
|
8099
|
-
if (
|
|
8578
|
+
if (existsSync11(targetDir)) {
|
|
8100
8579
|
rmSync5(targetDir, { recursive: true, force: true });
|
|
8101
8580
|
}
|
|
8102
8581
|
mkdirSync7(targetDir, { recursive: true });
|
|
@@ -8128,18 +8607,18 @@ async function downloadAndExtractMarketplaceTarball(baseUrl, slug, tmpDir, targe
|
|
|
8128
8607
|
}
|
|
8129
8608
|
if (tarballRes.status === 302 || tarballRes.headers.get("location")) {
|
|
8130
8609
|
const redirectUrl = tarballRes.headers.get("location");
|
|
8131
|
-
const tarballPath =
|
|
8610
|
+
const tarballPath = join12(tmpDir, `${slug}.tar.gz`);
|
|
8132
8611
|
await downloadToFile3(redirectUrl, tarballPath);
|
|
8133
|
-
const buffer =
|
|
8612
|
+
const buffer = readFileSync9(tarballPath);
|
|
8134
8613
|
const manifest = tryParseAsGzippedManifest(buffer);
|
|
8135
8614
|
if (manifest) {
|
|
8136
8615
|
extractManifestToDir(manifest, targetDir);
|
|
8137
8616
|
return;
|
|
8138
8617
|
}
|
|
8139
|
-
const extractDir =
|
|
8618
|
+
const extractDir = join12(tmpDir, "extracted");
|
|
8140
8619
|
extractTarGz3(tarballPath, extractDir);
|
|
8141
8620
|
flattenPackageRoot3(extractDir);
|
|
8142
|
-
if (
|
|
8621
|
+
if (existsSync11(targetDir)) {
|
|
8143
8622
|
rmSync5(targetDir, { recursive: true, force: true });
|
|
8144
8623
|
}
|
|
8145
8624
|
cpSync5(extractDir, targetDir, { recursive: true, force: true });
|
|
@@ -8150,13 +8629,13 @@ async function downloadAndExtractMarketplaceTarball(baseUrl, slug, tmpDir, targe
|
|
|
8150
8629
|
extractManifestToDir(manifest, targetDir);
|
|
8151
8630
|
return;
|
|
8152
8631
|
}
|
|
8153
|
-
const tarballPath =
|
|
8632
|
+
const tarballPath = join12(tmpDir, `${slug}.tar.gz`);
|
|
8154
8633
|
writeFileSync9(tarballPath, buffer);
|
|
8155
8634
|
try {
|
|
8156
|
-
const extractDir =
|
|
8635
|
+
const extractDir = join12(tmpDir, "extracted");
|
|
8157
8636
|
extractTarGz3(tarballPath, extractDir);
|
|
8158
8637
|
flattenPackageRoot3(extractDir);
|
|
8159
|
-
if (
|
|
8638
|
+
if (existsSync11(targetDir)) {
|
|
8160
8639
|
rmSync5(targetDir, { recursive: true, force: true });
|
|
8161
8640
|
}
|
|
8162
8641
|
cpSync5(extractDir, targetDir, { recursive: true, force: true });
|
|
@@ -8192,11 +8671,11 @@ function writeMarketplacePackageJson(plugin, slug, name, baseUrl, targetDir) {
|
|
|
8192
8671
|
}
|
|
8193
8672
|
};
|
|
8194
8673
|
const pkgPath = resolve7(targetDir, "package.json");
|
|
8195
|
-
if (!
|
|
8674
|
+
if (!existsSync11(pkgPath)) {
|
|
8196
8675
|
writeFileSync9(pkgPath, JSON.stringify(packageJson, null, 2));
|
|
8197
8676
|
} else {
|
|
8198
8677
|
try {
|
|
8199
|
-
const existing = JSON.parse(
|
|
8678
|
+
const existing = JSON.parse(readFileSync9(pkgPath, "utf-8"));
|
|
8200
8679
|
const merged = {
|
|
8201
8680
|
...existing,
|
|
8202
8681
|
xbrowser: { ...existing.xbrowser, ...packageJson.xbrowser },
|
|
@@ -8209,7 +8688,7 @@ function writeMarketplacePackageJson(plugin, slug, name, baseUrl, targetDir) {
|
|
|
8209
8688
|
}
|
|
8210
8689
|
}
|
|
8211
8690
|
function ensureIndexFile(plugin, name, targetDir) {
|
|
8212
|
-
if (!
|
|
8691
|
+
if (!existsSync11(resolve7(targetDir, "index.ts")) && !existsSync11(resolve7(targetDir, "index.js"))) {
|
|
8213
8692
|
const commands = plugin.commands || [];
|
|
8214
8693
|
const commandHandlers = commands.length > 0 ? commands.map((cmd) => {
|
|
8215
8694
|
return [
|
|
@@ -8263,7 +8742,7 @@ var PluginInstaller = class {
|
|
|
8263
8742
|
const type = this.detectSourceType(source);
|
|
8264
8743
|
const name = options?.name || this.deriveName(source, type);
|
|
8265
8744
|
const targetDir = resolve8(this.pluginsDir, name);
|
|
8266
|
-
if (
|
|
8745
|
+
if (existsSync12(targetDir) && !options?.force) {
|
|
8267
8746
|
throw new Error(`Plugin "${name}" already exists. Use --force to overwrite.`);
|
|
8268
8747
|
}
|
|
8269
8748
|
mkdirSync8(targetDir, { recursive: true });
|
|
@@ -8306,10 +8785,10 @@ var PluginInstaller = class {
|
|
|
8306
8785
|
*/
|
|
8307
8786
|
async fixSharedDeps(pluginDir) {
|
|
8308
8787
|
const indexPath = resolve8(pluginDir, "index.ts");
|
|
8309
|
-
if (!
|
|
8788
|
+
if (!existsSync12(indexPath)) return;
|
|
8310
8789
|
let content;
|
|
8311
8790
|
try {
|
|
8312
|
-
content =
|
|
8791
|
+
content = readFileSync10(indexPath, "utf8");
|
|
8313
8792
|
} catch {
|
|
8314
8793
|
return;
|
|
8315
8794
|
}
|
|
@@ -8324,7 +8803,7 @@ var PluginInstaller = class {
|
|
|
8324
8803
|
const toCopy = [];
|
|
8325
8804
|
for (const file of missingFiles) {
|
|
8326
8805
|
const targetPath = resolve8(sharedDir, file);
|
|
8327
|
-
if (!
|
|
8806
|
+
if (!existsSync12(targetPath)) {
|
|
8328
8807
|
toCopy.push(file);
|
|
8329
8808
|
}
|
|
8330
8809
|
}
|
|
@@ -8335,7 +8814,7 @@ var PluginInstaller = class {
|
|
|
8335
8814
|
];
|
|
8336
8815
|
let sourceSharedDir = null;
|
|
8337
8816
|
for (const dir of repoSharedDirs) {
|
|
8338
|
-
if (
|
|
8817
|
+
if (existsSync12(dir)) {
|
|
8339
8818
|
sourceSharedDir = dir;
|
|
8340
8819
|
break;
|
|
8341
8820
|
}
|
|
@@ -8352,7 +8831,7 @@ var PluginInstaller = class {
|
|
|
8352
8831
|
const content2 = await resp.text();
|
|
8353
8832
|
const dst = resolve8(sharedDir, file);
|
|
8354
8833
|
const dstDir = dirname4(dst);
|
|
8355
|
-
if (!
|
|
8834
|
+
if (!existsSync12(dstDir)) mkdirSync8(dstDir, { recursive: true });
|
|
8356
8835
|
writeFileSync10(dst, content2, "utf-8");
|
|
8357
8836
|
console.log(`\u2705 Downloaded shared/${file} from GitHub`);
|
|
8358
8837
|
} else {
|
|
@@ -8374,7 +8853,7 @@ var PluginInstaller = class {
|
|
|
8374
8853
|
for (const file of toCopy) {
|
|
8375
8854
|
const src = resolve8(sourceSharedDir, file);
|
|
8376
8855
|
const dst = resolve8(sharedDir, file);
|
|
8377
|
-
if (
|
|
8856
|
+
if (existsSync12(src)) {
|
|
8378
8857
|
try {
|
|
8379
8858
|
cpSync6(dirname4(src), dirname4(dst), { recursive: true });
|
|
8380
8859
|
console.log(`\u2705 Copied shared/${file} for plugin "${basename2(pluginDir)}"`);
|
|
@@ -8424,7 +8903,7 @@ var PluginInstaller = class {
|
|
|
8424
8903
|
*/
|
|
8425
8904
|
async uninstall(name) {
|
|
8426
8905
|
const targetDir = resolve8(this.pluginsDir, name);
|
|
8427
|
-
if (!
|
|
8906
|
+
if (!existsSync12(targetDir)) {
|
|
8428
8907
|
throw new Error(`Plugin "${name}" not found`);
|
|
8429
8908
|
}
|
|
8430
8909
|
rmSync6(targetDir, { recursive: true, force: true });
|
|
@@ -8435,7 +8914,7 @@ var PluginInstaller = class {
|
|
|
8435
8914
|
* @returns Array of installed plugin information.
|
|
8436
8915
|
*/
|
|
8437
8916
|
async list(_options) {
|
|
8438
|
-
if (!
|
|
8917
|
+
if (!existsSync12(this.pluginsDir)) return [];
|
|
8439
8918
|
const entries = readdirSync2(this.pluginsDir, { withFileTypes: true });
|
|
8440
8919
|
const plugins = [];
|
|
8441
8920
|
for (const entry of entries) {
|
|
@@ -8443,7 +8922,7 @@ var PluginInstaller = class {
|
|
|
8443
8922
|
const pluginPath = resolve8(this.pluginsDir, entry.name);
|
|
8444
8923
|
const indexPath = resolve8(pluginPath, "index.ts");
|
|
8445
8924
|
const indexJsPath = resolve8(pluginPath, "index.js");
|
|
8446
|
-
if (!
|
|
8925
|
+
if (!existsSync12(indexPath) && !existsSync12(indexJsPath)) continue;
|
|
8447
8926
|
const metadata = PluginMetadataParser.parseFromPackageJson(pluginPath);
|
|
8448
8927
|
let source = "local";
|
|
8449
8928
|
const pkg = readJsonFile(resolve8(pluginPath, "package.json"), {});
|
|
@@ -8470,10 +8949,10 @@ var PluginInstaller = class {
|
|
|
8470
8949
|
}
|
|
8471
8950
|
if (source.startsWith("file://")) {
|
|
8472
8951
|
const filePath = decodeURIComponent(new URL(source).pathname);
|
|
8473
|
-
if (
|
|
8952
|
+
if (existsSync12(filePath)) return "url";
|
|
8474
8953
|
}
|
|
8475
8954
|
if (source.endsWith(".git") || source.includes("github.com/")) return "git";
|
|
8476
|
-
if (
|
|
8955
|
+
if (existsSync12(resolve8(source))) return "local";
|
|
8477
8956
|
return "npm";
|
|
8478
8957
|
}
|
|
8479
8958
|
deriveName(source, type) {
|
|
@@ -9402,7 +9881,7 @@ var previewBuiltin = {
|
|
|
9402
9881
|
|
|
9403
9882
|
// src/builtins/knowledge.ts
|
|
9404
9883
|
init_site_knowledge();
|
|
9405
|
-
import { existsSync as
|
|
9884
|
+
import { existsSync as existsSync14 } from "fs";
|
|
9406
9885
|
var knowledgeBuiltin = {
|
|
9407
9886
|
name: "knowledge",
|
|
9408
9887
|
description: "View LLM-readable site knowledge base (selectors, forms, APIs)",
|
|
@@ -9581,8 +10060,8 @@ var knowledgeBuiltin = {
|
|
|
9581
10060
|
}
|
|
9582
10061
|
const mdPath = getKnowledgePath(domain, "md");
|
|
9583
10062
|
const jsonPath = getKnowledgePath(domain, "json");
|
|
9584
|
-
console.log(`Markdown: ${mdPath} (${
|
|
9585
|
-
console.log(`JSON: ${jsonPath} (${
|
|
10063
|
+
console.log(`Markdown: ${mdPath} (${existsSync14(mdPath) ? "exists" : "not found"})`);
|
|
10064
|
+
console.log(`JSON: ${jsonPath} (${existsSync14(jsonPath) ? "exists" : "not found"})`);
|
|
9586
10065
|
return;
|
|
9587
10066
|
}
|
|
9588
10067
|
console.error(`Unknown subcommand: ${subcommand}`);
|
|
@@ -9679,10 +10158,12 @@ var HTML_TAGS = /* @__PURE__ */ new Set([
|
|
|
9679
10158
|
"map",
|
|
9680
10159
|
"area"
|
|
9681
10160
|
]);
|
|
10161
|
+
var SELECTOR_DSL_PREFIXES = /^(text=|popup-text=|xpath=|role=|css=|chain=)/;
|
|
9682
10162
|
function normalizeSelector(input) {
|
|
9683
10163
|
if (!input) return input;
|
|
9684
10164
|
if (/^[#\.\[\:\/]/.test(input)) return input;
|
|
9685
10165
|
if (input === "*") return input;
|
|
10166
|
+
if (SELECTOR_DSL_PREFIXES.test(input)) return input;
|
|
9686
10167
|
if (/[>\s+,~]/.test(input)) return input;
|
|
9687
10168
|
const tagName = input.match(/^[a-zA-Z][a-zA-Z0-9]*/)?.[0];
|
|
9688
10169
|
if (tagName && HTML_TAGS.has(tagName.toLowerCase())) return input;
|
|
@@ -9857,11 +10338,29 @@ async function handleBrowserCommand(command, args, options, sessionName, mode, c
|
|
|
9857
10338
|
output: options.output || options.o || args[0]
|
|
9858
10339
|
};
|
|
9859
10340
|
break;
|
|
9860
|
-
case "
|
|
10341
|
+
case "ua": {
|
|
10342
|
+
cmdName = "ua";
|
|
10343
|
+
const val = args.join(" ").trim() || options.value || "";
|
|
10344
|
+
params = { value: val };
|
|
10345
|
+
break;
|
|
10346
|
+
}
|
|
10347
|
+
case "eval": {
|
|
9861
10348
|
cmdName = "eval";
|
|
9862
|
-
|
|
9863
|
-
|
|
10349
|
+
const frameVal = options.frame || void 0;
|
|
10350
|
+
const exprParts = [];
|
|
10351
|
+
for (let i = 0; i < args.length; i++) {
|
|
10352
|
+
if (args[i] === "--frame") {
|
|
10353
|
+
i++;
|
|
10354
|
+
continue;
|
|
10355
|
+
}
|
|
10356
|
+
if (args[i].startsWith("--frame=")) continue;
|
|
10357
|
+
exprParts.push(args[i]);
|
|
10358
|
+
}
|
|
10359
|
+
params = { expression: exprParts.join(" ") || options.expression || "" };
|
|
10360
|
+
if (frameVal) params.frame = frameVal;
|
|
10361
|
+
if (!params.expression) outputError("Usage: xbrowser eval <expression> [--frame <url-substring>]");
|
|
9864
10362
|
break;
|
|
10363
|
+
}
|
|
9865
10364
|
case "scroll": {
|
|
9866
10365
|
let direction = "down";
|
|
9867
10366
|
let distance;
|
|
@@ -10153,14 +10652,14 @@ async function handleBrowserCommand(command, args, options, sessionName, mode, c
|
|
|
10153
10652
|
|
|
10154
10653
|
// src/cli/session-routes.ts
|
|
10155
10654
|
import { homedir as homedir8 } from "os";
|
|
10156
|
-
import { join as
|
|
10655
|
+
import { join as join14 } from "path";
|
|
10157
10656
|
import { readdirSync as readdirSync3, rmSync as rmSync7 } from "fs";
|
|
10158
10657
|
function cleanSessionFiles() {
|
|
10159
|
-
const dir =
|
|
10658
|
+
const dir = join14(homedir8(), ".xbrowser", "sessions");
|
|
10160
10659
|
let count = 0;
|
|
10161
10660
|
try {
|
|
10162
10661
|
for (const entry of readdirSync3(dir, { withFileTypes: true })) {
|
|
10163
|
-
const p =
|
|
10662
|
+
const p = join14(dir, entry.name);
|
|
10164
10663
|
rmSync7(p, { recursive: true, force: true });
|
|
10165
10664
|
count++;
|
|
10166
10665
|
}
|
|
@@ -10564,7 +11063,7 @@ async function handlePlugin(args, options, mode) {
|
|
|
10564
11063
|
} catch {
|
|
10565
11064
|
}
|
|
10566
11065
|
try {
|
|
10567
|
-
const { daemonPing } = await import("./daemon-client-
|
|
11066
|
+
const { daemonPing } = await import("./daemon-client-HEGAXWGK.js");
|
|
10568
11067
|
if (await daemonPing()) {
|
|
10569
11068
|
await fetch("http://localhost:9224/rpc", {
|
|
10570
11069
|
method: "POST",
|
|
@@ -10714,7 +11213,7 @@ function handleDaemon(args, options, mode) {
|
|
|
10714
11213
|
}
|
|
10715
11214
|
|
|
10716
11215
|
// src/cli/record-routes.ts
|
|
10717
|
-
import { existsSync as
|
|
11216
|
+
import { existsSync as existsSync15 } from "fs";
|
|
10718
11217
|
async function handleRecord(args, options, mode) {
|
|
10719
11218
|
const sub = args[0];
|
|
10720
11219
|
switch (sub) {
|
|
@@ -10745,9 +11244,9 @@ async function handleRecord(args, options, mode) {
|
|
|
10745
11244
|
outputError(String(result.error || "Failed to stop recording"));
|
|
10746
11245
|
return;
|
|
10747
11246
|
}
|
|
10748
|
-
if (output && !
|
|
11247
|
+
if (output && !existsSync15(output)) {
|
|
10749
11248
|
const defaultRecording = SessionRecorder.getRecordingsDir(sessionName) + "/recording.json";
|
|
10750
|
-
if (
|
|
11249
|
+
if (existsSync15(defaultRecording)) {
|
|
10751
11250
|
try {
|
|
10752
11251
|
const { mkdirSync: mkdirSync10, copyFileSync: copyFileSync2 } = await import("fs");
|
|
10753
11252
|
const { dirname: pathDirname } = await import("path");
|
|
@@ -11130,10 +11629,10 @@ async function handleFilter(args, _mode, options) {
|
|
|
11130
11629
|
}
|
|
11131
11630
|
}
|
|
11132
11631
|
async function handleGeneratePlugin(sessionName, pluginName, outputDir) {
|
|
11133
|
-
const { SessionRecorder: SessionRecorder2 } = await import("./session-recorder-
|
|
11632
|
+
const { SessionRecorder: SessionRecorder2 } = await import("./session-recorder-SLDBENVF.js");
|
|
11134
11633
|
const { readSiteKnowledge: readSiteKnowledge2, toMarkdown } = await import("./site-knowledge-SYC6VCDB.js");
|
|
11135
11634
|
const { mkdirSync: mkdirSync10, writeFileSync: writeFileSync12 } = await import("fs");
|
|
11136
|
-
const { join:
|
|
11635
|
+
const { join: join15 } = await import("path");
|
|
11137
11636
|
const data = SessionRecorder2.readData(sessionName);
|
|
11138
11637
|
if (!data) {
|
|
11139
11638
|
outputError(`No recording found for session "${sessionName}". Run \`xbrowser record stop --session ${sessionName}\` first.`);
|
|
@@ -11145,14 +11644,14 @@ async function handleGeneratePlugin(sessionName, pluginName, outputDir) {
|
|
|
11145
11644
|
} catch {
|
|
11146
11645
|
}
|
|
11147
11646
|
const finalPluginName = pluginName || domain.split(".")[0] || "my-site";
|
|
11148
|
-
const finalOutputDir = outputDir ||
|
|
11647
|
+
const finalOutputDir = outputDir || join15(process.cwd(), ".xcli", "plugins", finalPluginName);
|
|
11149
11648
|
const knowledge = readSiteKnowledge2(domain);
|
|
11150
11649
|
const knowledgeMd = knowledge ? toMarkdown(knowledge) : "";
|
|
11151
11650
|
const pluginCode = generatePluginCode(finalPluginName, domain, data, knowledgeMd);
|
|
11152
|
-
mkdirSync10(
|
|
11153
|
-
writeFileSync12(
|
|
11651
|
+
mkdirSync10(join15(finalOutputDir), { recursive: true });
|
|
11652
|
+
writeFileSync12(join15(finalOutputDir, "index.ts"), pluginCode, "utf-8");
|
|
11154
11653
|
if (knowledgeMd) {
|
|
11155
|
-
writeFileSync12(
|
|
11654
|
+
writeFileSync12(join15(finalOutputDir, "SITE_KNOWLEDGE.md"), knowledgeMd, "utf-8");
|
|
11156
11655
|
}
|
|
11157
11656
|
console.log("");
|
|
11158
11657
|
console.log("=== Plugin Generated ===");
|
|
@@ -11695,7 +12194,7 @@ async function handleNetCommand(args, options, mode, sessionName) {
|
|
|
11695
12194
|
|
|
11696
12195
|
// src/cli/test-routes.ts
|
|
11697
12196
|
import { execSync as execSync3 } from "child_process";
|
|
11698
|
-
import { readFileSync as
|
|
12197
|
+
import { readFileSync as readFileSync11 } from "fs";
|
|
11699
12198
|
import { resolve as resolve9 } from "path";
|
|
11700
12199
|
function findPluginPath(plugin) {
|
|
11701
12200
|
const candidates = [
|
|
@@ -11704,7 +12203,7 @@ function findPluginPath(plugin) {
|
|
|
11704
12203
|
];
|
|
11705
12204
|
for (const p of candidates) {
|
|
11706
12205
|
try {
|
|
11707
|
-
|
|
12206
|
+
readFileSync11(p, "utf-8");
|
|
11708
12207
|
return p;
|
|
11709
12208
|
} catch {
|
|
11710
12209
|
}
|
|
@@ -11715,7 +12214,7 @@ function extractSchema(plugin, command) {
|
|
|
11715
12214
|
const pluginPath = findPluginPath(plugin);
|
|
11716
12215
|
let src;
|
|
11717
12216
|
try {
|
|
11718
|
-
src =
|
|
12217
|
+
src = readFileSync11(pluginPath, "utf-8");
|
|
11719
12218
|
} catch {
|
|
11720
12219
|
return null;
|
|
11721
12220
|
}
|
|
@@ -12436,6 +12935,9 @@ var HTTPServer = class {
|
|
|
12436
12935
|
this.server = null;
|
|
12437
12936
|
reject(err);
|
|
12438
12937
|
});
|
|
12938
|
+
server.headersTimeout = 20 * 6e4;
|
|
12939
|
+
server.requestTimeout = 20 * 6e4;
|
|
12940
|
+
server.keepAliveTimeout = 20 * 6e4;
|
|
12439
12941
|
server.listen(this.port, this.host, () => {
|
|
12440
12942
|
const addr = server.address();
|
|
12441
12943
|
if (addr && typeof addr === "object") {
|
|
@@ -12635,7 +13137,7 @@ function extractSessionNameFromArgv(argv) {
|
|
|
12635
13137
|
async function handleEvalMode(argv) {
|
|
12636
13138
|
const evalCommands = parseEvalFlags(argv);
|
|
12637
13139
|
if (evalCommands.length === 0) return;
|
|
12638
|
-
const chain = evalCommands.join(" ; ");
|
|
13140
|
+
const chain = evalCommands.map((script) => `eval --script-b64 ${Buffer.from(script, "utf8").toString("base64url")}`).join(" ; ");
|
|
12639
13141
|
const cdpEndpoint = extractCdpFromArgv(argv);
|
|
12640
13142
|
const sessionArgIdx = argv.indexOf("--session");
|
|
12641
13143
|
const sessionName = sessionArgIdx >= 0 && argv[sessionArgIdx + 1] ? argv[sessionArgIdx + 1] : process.env.XBROWSER_SESSION || "default";
|
|
@@ -12643,6 +13145,51 @@ async function handleEvalMode(argv) {
|
|
|
12643
13145
|
printChainResult(chainResult);
|
|
12644
13146
|
if (!chainResult.success) throw new Error("Command failed");
|
|
12645
13147
|
}
|
|
13148
|
+
function findEvalStart(argv) {
|
|
13149
|
+
const flagsWithValue = /* @__PURE__ */ new Set(["--session", "--cdp"]);
|
|
13150
|
+
for (let i = 0; i < argv.length; i++) {
|
|
13151
|
+
const a = argv[i];
|
|
13152
|
+
if (a === "-e" || a === "--eval") return -1;
|
|
13153
|
+
if (a.startsWith("--")) {
|
|
13154
|
+
if (flagsWithValue.has(a)) {
|
|
13155
|
+
i++;
|
|
13156
|
+
continue;
|
|
13157
|
+
}
|
|
13158
|
+
continue;
|
|
13159
|
+
}
|
|
13160
|
+
return a === "eval" ? i : -1;
|
|
13161
|
+
}
|
|
13162
|
+
return -1;
|
|
13163
|
+
}
|
|
13164
|
+
async function handleEvalScript(parts, argv) {
|
|
13165
|
+
const cdpEndpoint = extractCdpFromArgv(argv);
|
|
13166
|
+
const sessionName = extractSessionNameFromArgv(argv);
|
|
13167
|
+
let frame;
|
|
13168
|
+
const scriptLines = [];
|
|
13169
|
+
for (let i = 0; i < parts.length; i++) {
|
|
13170
|
+
const part = parts[i];
|
|
13171
|
+
if (part === "--frame" && parts[i + 1]) {
|
|
13172
|
+
frame = parts[i + 1];
|
|
13173
|
+
i++;
|
|
13174
|
+
continue;
|
|
13175
|
+
}
|
|
13176
|
+
if (part === "--session" || part === "--cdp") {
|
|
13177
|
+
i++;
|
|
13178
|
+
continue;
|
|
13179
|
+
}
|
|
13180
|
+
scriptLines.push(part);
|
|
13181
|
+
}
|
|
13182
|
+
const script = scriptLines.join("\n");
|
|
13183
|
+
if (!script.trim()) {
|
|
13184
|
+
console.error("Error: eval requires a script (usage: xbrowser eval <js...>)");
|
|
13185
|
+
throw new Error("Command failed");
|
|
13186
|
+
}
|
|
13187
|
+
const b64 = Buffer.from(script, "utf8").toString("base64url");
|
|
13188
|
+
const chain = frame ? `eval --frame ${frame} --script-b64 ${b64}` : `eval --script-b64 ${b64}`;
|
|
13189
|
+
const chainResult = await executeChain(chain, { cdpEndpoint, sessionName });
|
|
13190
|
+
printChainResult(chainResult);
|
|
13191
|
+
if (!chainResult.success) throw new Error("Command failed");
|
|
13192
|
+
}
|
|
12646
13193
|
async function handleChainInput(input, argv) {
|
|
12647
13194
|
const cdpEndpoint = argv ? extractCdpFromArgv(argv) : void 0;
|
|
12648
13195
|
const hasJson = argv ? argv.some((a) => a === "--json" || a.startsWith("--json=") || a.includes(" --json") || a.startsWith("--json")) || argv.includes("-j") : false;
|
|
@@ -12680,6 +13227,11 @@ async function routeCommand(argvIn, stdinCommands) {
|
|
|
12680
13227
|
await handleStdinMode(stdinCommands, argv);
|
|
12681
13228
|
return;
|
|
12682
13229
|
}
|
|
13230
|
+
const evalStart = findEvalStart(argv);
|
|
13231
|
+
if (evalStart >= 0 && argv.length > evalStart + 1) {
|
|
13232
|
+
await handleEvalScript(argv.slice(evalStart + 1), argv);
|
|
13233
|
+
return;
|
|
13234
|
+
}
|
|
12683
13235
|
if (parseEvalFlags(argv).length > 0) {
|
|
12684
13236
|
await handleEvalMode(argv);
|
|
12685
13237
|
return;
|
|
@@ -13071,7 +13623,7 @@ Run "xbrowser ${command} ${subCommand} --help" to see available parameters.`
|
|
|
13071
13623
|
}
|
|
13072
13624
|
const needsBrowser = cmdEntry.scope === "page" || cmdEntry.scope === "browser";
|
|
13073
13625
|
if (needsBrowser && !process.env.XBROWSER_DAEMON_WORKER) {
|
|
13074
|
-
const { forwardPluginExec } = await import("./daemon-client-
|
|
13626
|
+
const { forwardPluginExec } = await import("./daemon-client-HEGAXWGK.js");
|
|
13075
13627
|
const userTimeout = typeof params.timeout === "number" && params.timeout > 0 ? params.timeout * 1e3 + 3e4 : void 0;
|
|
13076
13628
|
const result = await forwardPluginExec(`${command}.${subCommand}`, params, sessionName, cdpEndpoint, userTimeout);
|
|
13077
13629
|
const resultData = result && typeof result === "object" && "data" in result ? result.data : void 0;
|
|
@@ -13111,7 +13663,7 @@ Run "xbrowser ${command} ${subCommand} --help" to see available parameters.`
|
|
|
13111
13663
|
const targetPage = pages[cmdTabIndex];
|
|
13112
13664
|
await targetPage.bringToFront().catch(() => {
|
|
13113
13665
|
});
|
|
13114
|
-
const { setActivePage: setActivePage2 } = await import("./browser-
|
|
13666
|
+
const { setActivePage: setActivePage2 } = await import("./browser-ILXTEWH4.js");
|
|
13115
13667
|
setActivePage2(session, targetPage);
|
|
13116
13668
|
}
|
|
13117
13669
|
}
|
|
@@ -13387,7 +13939,7 @@ async function main() {
|
|
|
13387
13939
|
const command = process.argv[2];
|
|
13388
13940
|
const isLongRunning = command === "preview" || command === "serve";
|
|
13389
13941
|
if (!isLongRunning) {
|
|
13390
|
-
const { ensureProcessCanExit } = await import("./browser-
|
|
13942
|
+
const { ensureProcessCanExit } = await import("./browser-ILXTEWH4.js");
|
|
13391
13943
|
await ensureProcessCanExit().catch(() => {
|
|
13392
13944
|
});
|
|
13393
13945
|
process.exit(process.exitCode || exitCode);
|