@xbrowser/cli 0.15.1 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{browser-R7B255ML.js → browser-53KUFEEM.js} +5 -1
- package/dist/{browser-I2HJZ7IP.js → browser-DSVV4GHS.js} +2 -2
- package/dist/{browser-GWBH6OJK.js → browser-GURRY444.js} +3 -1
- package/dist/cdp-driver-MNPR3HZH.js +2537 -0
- package/dist/cdp-driver-SSXUGXP6.js +47 -0
- package/dist/{chunk-2ONMTDLK.js → chunk-2BQZIT3S.js} +2535 -50
- package/dist/{chunk-KDYXFLAC.js → chunk-2MFXKN32.js} +2 -2
- package/dist/chunk-42RPMJ76.js +2530 -0
- package/dist/{chunk-F3ZWFCJJ.js → chunk-E4O5ZU3H.js} +2535 -50
- package/dist/{chunk-ATFTAKMN.js → chunk-IDVD44ED.js} +20 -0
- package/dist/chunk-T4J4C2NZ.js +250 -0
- package/dist/{chunk-RS6YYWTK.js → chunk-YKOHDEFV.js} +73 -38
- package/dist/cli.js +1176 -281
- package/dist/{convert-4DUWZIKH.js → convert-EGFYNICZ.js} +2 -0
- package/dist/{daemon-client-GX2UYIW4.js → daemon-client-3VM7VU7O.js} +22 -0
- package/dist/{daemon-client-3IJD6X4B.js → daemon-client-YAVQ343A.js} +7 -1
- package/dist/daemon-main.js +1004 -161
- package/dist/{extract-EGRXZSSK.js → extract-L2IW3IUB.js} +2 -0
- package/dist/{filter-OLAE26HN.js → filter-HC4RA7JY.js} +2 -0
- package/dist/index.d.ts +581 -41
- package/dist/index.js +1220 -321
- package/dist/launcher-KA7J32K5.js +19 -0
- package/dist/{network-store-YAF5OIBH.js → network-store-66A2RATI.js} +1 -0
- package/dist/{session-recorder-XET3DNML.js → session-recorder-MA75PKTQ.js} +1 -1
- package/package.json +3 -4
- package/dist/daemon-client-XWSSQBEA.js +0 -58
- package/dist/network-store-2S5HATEV.js +0 -194
- package/dist/parse-action-dsl-DRSPBALP.js +0 -72
- package/dist/screenshot-MB6R7RSS.js +0 -26
- package/dist/session-recorder-ILSSV2UC.js +0 -6
package/dist/index.js
CHANGED
|
@@ -1,3 +1,47 @@
|
|
|
1
|
+
import {
|
|
2
|
+
forwardCommandLog,
|
|
3
|
+
forwardNetworkAnalyze,
|
|
4
|
+
forwardNetworkAround,
|
|
5
|
+
forwardNetworkClear,
|
|
6
|
+
forwardNetworkCurl,
|
|
7
|
+
forwardNetworkDislike,
|
|
8
|
+
forwardNetworkExport,
|
|
9
|
+
forwardNetworkInspect,
|
|
10
|
+
forwardNetworkLike,
|
|
11
|
+
forwardNetworkList,
|
|
12
|
+
forwardNetworkReplay,
|
|
13
|
+
forwardNetworkTop,
|
|
14
|
+
forwardRecordCheckpoint,
|
|
15
|
+
forwardRecordStart,
|
|
16
|
+
forwardRecordStatus,
|
|
17
|
+
forwardRecordStop,
|
|
18
|
+
forwardRecordSummary,
|
|
19
|
+
forwardReplay,
|
|
20
|
+
forwardSessionClose,
|
|
21
|
+
forwardSessionCreate,
|
|
22
|
+
forwardSessionList,
|
|
23
|
+
forwardViewerCheckSelector,
|
|
24
|
+
getDaemonConfig,
|
|
25
|
+
getDaemonProcessStatus,
|
|
26
|
+
killAllDaemonProcesses,
|
|
27
|
+
startDaemonProcess,
|
|
28
|
+
stopDaemonProcess
|
|
29
|
+
} from "./chunk-IDVD44ED.js";
|
|
30
|
+
import {
|
|
31
|
+
CaptchaDetector,
|
|
32
|
+
HumanInteractionManager,
|
|
33
|
+
NPM_REGISTRY_URL,
|
|
34
|
+
NPM_SCOPE,
|
|
35
|
+
ScreencastCapturer,
|
|
36
|
+
WebhookNotifier,
|
|
37
|
+
getCaptchaConfig,
|
|
38
|
+
getConfigValue,
|
|
39
|
+
getMarketplaceUrl,
|
|
40
|
+
loadConfig,
|
|
41
|
+
readJsonFile,
|
|
42
|
+
resolveNpmPackageWithFallback,
|
|
43
|
+
setConfigValue
|
|
44
|
+
} from "./chunk-ITKPSIP7.js";
|
|
1
45
|
import {
|
|
2
46
|
generateBashScript,
|
|
3
47
|
generateJSScript,
|
|
@@ -14,7 +58,7 @@ import {
|
|
|
14
58
|
} from "./chunk-OPRXFZVE.js";
|
|
15
59
|
import {
|
|
16
60
|
SessionRecorder
|
|
17
|
-
} from "./chunk-
|
|
61
|
+
} from "./chunk-2MFXKN32.js";
|
|
18
62
|
import {
|
|
19
63
|
closeAllSessions,
|
|
20
64
|
closeEphemeralContext,
|
|
@@ -31,7 +75,9 @@ import {
|
|
|
31
75
|
resolveLaunchOpts,
|
|
32
76
|
saveSessionDiskMeta,
|
|
33
77
|
setActivePage
|
|
34
|
-
} from "./chunk-
|
|
78
|
+
} from "./chunk-YKOHDEFV.js";
|
|
79
|
+
import "./chunk-42RPMJ76.js";
|
|
80
|
+
import "./chunk-T4J4C2NZ.js";
|
|
35
81
|
import {
|
|
36
82
|
CDPInterceptorProxy,
|
|
37
83
|
advise,
|
|
@@ -46,50 +92,6 @@ import {
|
|
|
46
92
|
networkAnomalyRule,
|
|
47
93
|
pageLifecycleRule
|
|
48
94
|
} from "./chunk-ZZ2TFWIV.js";
|
|
49
|
-
import {
|
|
50
|
-
forwardCommandLog,
|
|
51
|
-
forwardNetworkAnalyze,
|
|
52
|
-
forwardNetworkAround,
|
|
53
|
-
forwardNetworkClear,
|
|
54
|
-
forwardNetworkCurl,
|
|
55
|
-
forwardNetworkDislike,
|
|
56
|
-
forwardNetworkExport,
|
|
57
|
-
forwardNetworkInspect,
|
|
58
|
-
forwardNetworkLike,
|
|
59
|
-
forwardNetworkList,
|
|
60
|
-
forwardNetworkReplay,
|
|
61
|
-
forwardNetworkTop,
|
|
62
|
-
forwardRecordCheckpoint,
|
|
63
|
-
forwardRecordStart,
|
|
64
|
-
forwardRecordStatus,
|
|
65
|
-
forwardRecordStop,
|
|
66
|
-
forwardRecordSummary,
|
|
67
|
-
forwardReplay,
|
|
68
|
-
forwardSessionClose,
|
|
69
|
-
forwardSessionCreate,
|
|
70
|
-
forwardSessionList,
|
|
71
|
-
forwardViewerCheckSelector,
|
|
72
|
-
getDaemonConfig,
|
|
73
|
-
getDaemonProcessStatus,
|
|
74
|
-
killAllDaemonProcesses,
|
|
75
|
-
startDaemonProcess,
|
|
76
|
-
stopDaemonProcess
|
|
77
|
-
} from "./chunk-ATFTAKMN.js";
|
|
78
|
-
import {
|
|
79
|
-
CaptchaDetector,
|
|
80
|
-
HumanInteractionManager,
|
|
81
|
-
NPM_REGISTRY_URL,
|
|
82
|
-
NPM_SCOPE,
|
|
83
|
-
ScreencastCapturer,
|
|
84
|
-
WebhookNotifier,
|
|
85
|
-
getCaptchaConfig,
|
|
86
|
-
getConfigValue,
|
|
87
|
-
getMarketplaceUrl,
|
|
88
|
-
loadConfig,
|
|
89
|
-
readJsonFile,
|
|
90
|
-
resolveNpmPackageWithFallback,
|
|
91
|
-
setConfigValue
|
|
92
|
-
} from "./chunk-ITKPSIP7.js";
|
|
93
95
|
import {
|
|
94
96
|
__require
|
|
95
97
|
} from "./chunk-3RG5ZIWI.js";
|
|
@@ -102,12 +104,13 @@ var version = pkg.version;
|
|
|
102
104
|
|
|
103
105
|
// src/executor.ts
|
|
104
106
|
import {
|
|
105
|
-
ok as
|
|
107
|
+
ok as ok26,
|
|
106
108
|
fail as fail7,
|
|
107
109
|
isCommandResult,
|
|
108
110
|
configureArchiveStore,
|
|
109
111
|
appendCommandToArchive,
|
|
110
|
-
checkGuard
|
|
112
|
+
checkGuard,
|
|
113
|
+
PluginStorage
|
|
111
114
|
} from "@dyyz1993/xcli-core";
|
|
112
115
|
|
|
113
116
|
// src/utils/positional-params.ts
|
|
@@ -580,7 +583,7 @@ var pressCommand = registerCommand({
|
|
|
580
583
|
key: z2.string()
|
|
581
584
|
}),
|
|
582
585
|
handler: async (p, ctx) => {
|
|
583
|
-
await ctx.page.press(p.selector || "body", p.key, {
|
|
586
|
+
await ctx.page.press(p.selector || "body", p.key, { timeout: 1e4 });
|
|
584
587
|
return ok2({ key: p.key });
|
|
585
588
|
}
|
|
586
589
|
});
|
|
@@ -599,7 +602,7 @@ var selectCommand = registerCommand({
|
|
|
599
602
|
}),
|
|
600
603
|
handler: async (p, ctx) => {
|
|
601
604
|
const values = typeof p.value === "string" ? [p.value] : p.value;
|
|
602
|
-
await ctx.page.selectOption(p.selector, values
|
|
605
|
+
await ctx.page.selectOption(p.selector, values);
|
|
603
606
|
return ok2({ selector: p.selector, value: p.value });
|
|
604
607
|
}
|
|
605
608
|
});
|
|
@@ -615,7 +618,7 @@ var checkCommand = registerCommand({
|
|
|
615
618
|
selector: z2.string()
|
|
616
619
|
}),
|
|
617
620
|
handler: async (p, ctx) => {
|
|
618
|
-
await ctx.page.check(p.selector, {
|
|
621
|
+
await ctx.page.check(p.selector, { timeout: 1e4 });
|
|
619
622
|
return ok2({ selector: p.selector });
|
|
620
623
|
}
|
|
621
624
|
});
|
|
@@ -632,7 +635,7 @@ var hoverCommand = registerCommand({
|
|
|
632
635
|
selector: z2.string()
|
|
633
636
|
}),
|
|
634
637
|
handler: async (p, ctx) => {
|
|
635
|
-
await ctx.page.hover(p.selector, {
|
|
638
|
+
await ctx.page.hover(p.selector, { timeout: 1e4 });
|
|
636
639
|
return ok2({ selector: p.selector });
|
|
637
640
|
}
|
|
638
641
|
});
|
|
@@ -2161,7 +2164,7 @@ var scrapeCommand = registerCommand({
|
|
|
2161
2164
|
await page.goto(p.url, { waitUntil: "commit", timeout: p.timeout });
|
|
2162
2165
|
await page.waitForSelector("body", { timeout: p.timeout }).catch(() => {
|
|
2163
2166
|
});
|
|
2164
|
-
await page.waitForLoadState("networkidle",
|
|
2167
|
+
await page.waitForLoadState("networkidle", Math.min(p.timeout, 8e3)).catch(() => {
|
|
2165
2168
|
});
|
|
2166
2169
|
await page.waitForTimeout(p.waitAfterLoad > 0 ? p.waitAfterLoad : 2e3);
|
|
2167
2170
|
if (p.selector) {
|
|
@@ -3166,7 +3169,7 @@ async function fetchFullContent(urls, timeout, cdpEndpoint) {
|
|
|
3166
3169
|
const pg = await context.newPage();
|
|
3167
3170
|
try {
|
|
3168
3171
|
await pg.goto(url, { waitUntil: "domcontentloaded", timeout });
|
|
3169
|
-
await pg.waitForLoadState("networkidle",
|
|
3172
|
+
await pg.waitForLoadState("networkidle", timeout).catch(() => {
|
|
3170
3173
|
});
|
|
3171
3174
|
const html = await pg.content();
|
|
3172
3175
|
contentMap.set(url, htmlToMarkdown(html, { onlyMainContent: true }));
|
|
@@ -3562,7 +3565,7 @@ var networkCommand = registerCommand({
|
|
|
3562
3565
|
waitUntil: "domcontentloaded",
|
|
3563
3566
|
timeout: p.timeout
|
|
3564
3567
|
});
|
|
3565
|
-
await page.waitForLoadState("networkidle",
|
|
3568
|
+
await page.waitForLoadState("networkidle", p.timeout).catch(() => {
|
|
3566
3569
|
});
|
|
3567
3570
|
await page.waitForTimeout(p.wait);
|
|
3568
3571
|
const totalCount = captures.length;
|
|
@@ -4150,6 +4153,24 @@ function parseMarkdownResults(rawText) {
|
|
|
4150
4153
|
import { z as z21 } from "zod";
|
|
4151
4154
|
import { ok as ok20, fail as fail4 } from "@dyyz1993/xcli-core";
|
|
4152
4155
|
|
|
4156
|
+
// src/runtime/ref-store.ts
|
|
4157
|
+
var sessions = /* @__PURE__ */ new Map();
|
|
4158
|
+
function normalizeAgentRef(ref) {
|
|
4159
|
+
return ref.startsWith("@") ? ref.slice(1) : ref;
|
|
4160
|
+
}
|
|
4161
|
+
function replaceRefs(sessionKey2, screenHash, targets) {
|
|
4162
|
+
sessions.set(sessionKey2, {
|
|
4163
|
+
screenHash,
|
|
4164
|
+
targets: new Map(targets.map((target) => [target.ref, target]))
|
|
4165
|
+
});
|
|
4166
|
+
}
|
|
4167
|
+
function getRefTarget(sessionKey2, ref) {
|
|
4168
|
+
const session = sessions.get(sessionKey2);
|
|
4169
|
+
const target = session?.targets.get(normalizeAgentRef(ref));
|
|
4170
|
+
if (!session || !target) return null;
|
|
4171
|
+
return { screenHash: session.screenHash, target };
|
|
4172
|
+
}
|
|
4173
|
+
|
|
4153
4174
|
// src/utils/resolve-selector.ts
|
|
4154
4175
|
function buildElementSelector(el) {
|
|
4155
4176
|
function isUnique(sel) {
|
|
@@ -4321,9 +4342,9 @@ async function resolveSelectors(page, ariaSnapshot) {
|
|
|
4321
4342
|
}
|
|
4322
4343
|
return results;
|
|
4323
4344
|
}
|
|
4324
|
-
var REF_ONLY =
|
|
4345
|
+
var REF_ONLY = /^@?(e\d+)$/;
|
|
4325
4346
|
var refCache = /* @__PURE__ */ new Map();
|
|
4326
|
-
async function resolveRefParams(page, params, selectorKeys, cache) {
|
|
4347
|
+
async function resolveRefParams(page, params, selectorKeys, cache, sessionId) {
|
|
4327
4348
|
const tips = [];
|
|
4328
4349
|
const newParams = { ...params };
|
|
4329
4350
|
if (!selectorKeys || selectorKeys.length === 0) {
|
|
@@ -4332,7 +4353,17 @@ async function resolveRefParams(page, params, selectorKeys, cache) {
|
|
|
4332
4353
|
for (const key of selectorKeys) {
|
|
4333
4354
|
const val = params[key];
|
|
4334
4355
|
if (typeof val !== "string" || !REF_ONLY.test(val)) continue;
|
|
4335
|
-
const
|
|
4356
|
+
const match = val.match(REF_ONLY);
|
|
4357
|
+
if (!match) continue;
|
|
4358
|
+
const ref = normalizeAgentRef(match[1]);
|
|
4359
|
+
if (sessionId) {
|
|
4360
|
+
const runtimeTarget = getRefTarget(sessionId, ref);
|
|
4361
|
+
if (runtimeTarget) {
|
|
4362
|
+
tips.push(`ref=@${ref} (${key}) => ${runtimeTarget.target.selector} (observe)`);
|
|
4363
|
+
newParams[key] = runtimeTarget.target.selector;
|
|
4364
|
+
continue;
|
|
4365
|
+
}
|
|
4366
|
+
}
|
|
4336
4367
|
const activeCache = cache ?? refCache;
|
|
4337
4368
|
const cached = activeCache.get(ref);
|
|
4338
4369
|
if (cached) {
|
|
@@ -4613,6 +4644,404 @@ async function enhanceSemanticsWithLLM(url, ariaSnapshot, ruleBasedElements) {
|
|
|
4613
4644
|
saveSemantics(domain, pathKey, url, llmElements);
|
|
4614
4645
|
}
|
|
4615
4646
|
|
|
4647
|
+
// src/runtime/agent-runtime.ts
|
|
4648
|
+
function sessionKey(sessionId) {
|
|
4649
|
+
return sessionId || "default";
|
|
4650
|
+
}
|
|
4651
|
+
async function observePage(page, sessionId, options = {}) {
|
|
4652
|
+
const [title, raw] = await Promise.all([
|
|
4653
|
+
page.title().catch(() => ""),
|
|
4654
|
+
page.evaluate(
|
|
4655
|
+
({ includeHidden, limit }) => {
|
|
4656
|
+
function hash(input) {
|
|
4657
|
+
let h = 2166136261;
|
|
4658
|
+
for (let i = 0; i < input.length; i++) {
|
|
4659
|
+
h ^= input.charCodeAt(i);
|
|
4660
|
+
h = Math.imul(h, 16777619);
|
|
4661
|
+
}
|
|
4662
|
+
return (h >>> 0).toString(16);
|
|
4663
|
+
}
|
|
4664
|
+
function cssEscape(value) {
|
|
4665
|
+
const css = globalThis.CSS;
|
|
4666
|
+
return css?.escape ? css.escape(value) : value.replace(/["\\#.:,[\]>+~*]/g, "\\$&");
|
|
4667
|
+
}
|
|
4668
|
+
function isUnique(selector2) {
|
|
4669
|
+
try {
|
|
4670
|
+
return document.querySelectorAll(selector2).length === 1;
|
|
4671
|
+
} catch {
|
|
4672
|
+
return false;
|
|
4673
|
+
}
|
|
4674
|
+
}
|
|
4675
|
+
function nthOfType(el) {
|
|
4676
|
+
const tag = el.tagName.toLowerCase();
|
|
4677
|
+
const parent = el.parentElement;
|
|
4678
|
+
if (!parent) return tag;
|
|
4679
|
+
const same = Array.from(parent.children).filter((child) => child.tagName === el.tagName);
|
|
4680
|
+
if (same.length === 1) return tag;
|
|
4681
|
+
return `${tag}:nth-of-type(${same.indexOf(el) + 1})`;
|
|
4682
|
+
}
|
|
4683
|
+
function selectorFor(el) {
|
|
4684
|
+
const tag = el.tagName.toLowerCase();
|
|
4685
|
+
const id = el.getAttribute("id");
|
|
4686
|
+
if (id) {
|
|
4687
|
+
const selector2 = `#${cssEscape(id)}`;
|
|
4688
|
+
if (isUnique(selector2)) return selector2;
|
|
4689
|
+
}
|
|
4690
|
+
for (const attr of ["data-testid", "data-test", "data-qa", "name", "aria-label"]) {
|
|
4691
|
+
const value = el.getAttribute(attr);
|
|
4692
|
+
if (!value) continue;
|
|
4693
|
+
const selector2 = `[${attr}="${cssEscape(value)}"]`;
|
|
4694
|
+
if (isUnique(selector2)) return selector2;
|
|
4695
|
+
const tagged = `${tag}${selector2}`;
|
|
4696
|
+
if (isUnique(tagged)) return tagged;
|
|
4697
|
+
}
|
|
4698
|
+
const classes = Array.from(el.classList).slice(0, 3);
|
|
4699
|
+
for (const cls of classes) {
|
|
4700
|
+
const selector2 = `${tag}.${cssEscape(cls)}`;
|
|
4701
|
+
if (isUnique(selector2)) return selector2;
|
|
4702
|
+
}
|
|
4703
|
+
const parts = [];
|
|
4704
|
+
let cur = el;
|
|
4705
|
+
while (cur && cur !== document.body && cur !== document.documentElement && parts.length < 6) {
|
|
4706
|
+
parts.unshift(nthOfType(cur));
|
|
4707
|
+
const selector2 = parts.join(" > ");
|
|
4708
|
+
if (isUnique(selector2)) return selector2;
|
|
4709
|
+
cur = cur.parentElement;
|
|
4710
|
+
}
|
|
4711
|
+
return parts.join(" > ") || tag;
|
|
4712
|
+
}
|
|
4713
|
+
function roleFor(el) {
|
|
4714
|
+
const explicit = el.getAttribute("role");
|
|
4715
|
+
if (explicit) return explicit;
|
|
4716
|
+
const tag = el.tagName.toLowerCase();
|
|
4717
|
+
if (tag === "a") return "link";
|
|
4718
|
+
if (tag === "button") return "button";
|
|
4719
|
+
if (tag === "select") return "combobox";
|
|
4720
|
+
if (tag === "textarea") return "textbox";
|
|
4721
|
+
if (tag === "input") {
|
|
4722
|
+
const type = (el.getAttribute("type") || "text").toLowerCase();
|
|
4723
|
+
if (type === "checkbox") return "checkbox";
|
|
4724
|
+
if (type === "radio") return "radio";
|
|
4725
|
+
if (type === "button" || type === "submit" || type === "reset") return "button";
|
|
4726
|
+
return "textbox";
|
|
4727
|
+
}
|
|
4728
|
+
return tag;
|
|
4729
|
+
}
|
|
4730
|
+
function textName(el) {
|
|
4731
|
+
const input = el;
|
|
4732
|
+
const direct = el.getAttribute("aria-label") || el.getAttribute("placeholder") || el.getAttribute("title") || input.value || el.textContent || "";
|
|
4733
|
+
return direct.replace(/\s+/g, " ").trim().slice(0, 120);
|
|
4734
|
+
}
|
|
4735
|
+
function actionsFor(role, editable) {
|
|
4736
|
+
const actions = [];
|
|
4737
|
+
if (editable) actions.push("fill", "type");
|
|
4738
|
+
if (role === "combobox") actions.push("select");
|
|
4739
|
+
if (role === "checkbox" || role === "radio") actions.push("check");
|
|
4740
|
+
actions.push("click", "hover");
|
|
4741
|
+
return Array.from(new Set(actions));
|
|
4742
|
+
}
|
|
4743
|
+
const selector = [
|
|
4744
|
+
"a[href]",
|
|
4745
|
+
"button",
|
|
4746
|
+
"input",
|
|
4747
|
+
"textarea",
|
|
4748
|
+
"select",
|
|
4749
|
+
"summary",
|
|
4750
|
+
"label",
|
|
4751
|
+
"[role]",
|
|
4752
|
+
"[tabindex]",
|
|
4753
|
+
'[contenteditable="true"]'
|
|
4754
|
+
].join(",");
|
|
4755
|
+
const candidates = Array.from(document.querySelectorAll(selector));
|
|
4756
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4757
|
+
const targets2 = [];
|
|
4758
|
+
for (const el of candidates) {
|
|
4759
|
+
const rect = el.getBoundingClientRect();
|
|
4760
|
+
const style = window.getComputedStyle(el);
|
|
4761
|
+
const visible = rect.width > 0 && rect.height > 0 && style.visibility !== "hidden" && style.display !== "none";
|
|
4762
|
+
if (!includeHidden && !visible) continue;
|
|
4763
|
+
const selectorValue = selectorFor(el);
|
|
4764
|
+
if (!selectorValue || seen.has(selectorValue)) continue;
|
|
4765
|
+
seen.add(selectorValue);
|
|
4766
|
+
const input = el;
|
|
4767
|
+
const tag = el.tagName.toLowerCase();
|
|
4768
|
+
const role = roleFor(el);
|
|
4769
|
+
const editable = tag === "textarea" || tag === "select" || tag === "input" && !["button", "submit", "reset", "checkbox", "radio"].includes((input.type || "").toLowerCase()) || el.isContentEditable;
|
|
4770
|
+
const enabled = !input.disabled && el.getAttribute("aria-disabled") !== "true";
|
|
4771
|
+
const checked = typeof input.checked === "boolean" && ["checkbox", "radio"].includes((input.type || "").toLowerCase()) ? input.checked : void 0;
|
|
4772
|
+
targets2.push({
|
|
4773
|
+
selector: selectorValue,
|
|
4774
|
+
role,
|
|
4775
|
+
name: textName(el),
|
|
4776
|
+
tag,
|
|
4777
|
+
visible,
|
|
4778
|
+
enabled,
|
|
4779
|
+
editable,
|
|
4780
|
+
...checked !== void 0 ? { checked } : {},
|
|
4781
|
+
...editable && input.value ? { value: input.value.slice(0, 120) } : {},
|
|
4782
|
+
...visible ? { box: { x: Math.round(rect.x), y: Math.round(rect.y), width: Math.round(rect.width), height: Math.round(rect.height) } } : {},
|
|
4783
|
+
actions: actionsFor(role, editable)
|
|
4784
|
+
});
|
|
4785
|
+
if (targets2.length >= limit) break;
|
|
4786
|
+
}
|
|
4787
|
+
const stateText = [
|
|
4788
|
+
location.href,
|
|
4789
|
+
document.title,
|
|
4790
|
+
document.body?.innerText?.slice(0, 5e3) || ""
|
|
4791
|
+
].join("\n");
|
|
4792
|
+
return { screenHash: hash(stateText), targets: targets2 };
|
|
4793
|
+
},
|
|
4794
|
+
{ includeHidden: !!options.includeHidden, limit: options.limit ?? 80 }
|
|
4795
|
+
)
|
|
4796
|
+
]);
|
|
4797
|
+
const targets = raw.targets.map((target, index) => ({
|
|
4798
|
+
ref: `e${index + 1}`,
|
|
4799
|
+
...target
|
|
4800
|
+
}));
|
|
4801
|
+
replaceRefs(sessionKey(sessionId), raw.screenHash, targets);
|
|
4802
|
+
return {
|
|
4803
|
+
url: page.url(),
|
|
4804
|
+
title,
|
|
4805
|
+
screenHash: raw.screenHash,
|
|
4806
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4807
|
+
targets
|
|
4808
|
+
};
|
|
4809
|
+
}
|
|
4810
|
+
function quoteName(name) {
|
|
4811
|
+
const cleaned = name.replace(/\s+/g, " ").trim();
|
|
4812
|
+
if (!cleaned) return "";
|
|
4813
|
+
return ` "${cleaned.replace(/"/g, '\\"')}"`;
|
|
4814
|
+
}
|
|
4815
|
+
function targetFlags(target) {
|
|
4816
|
+
const flags = [target.role || target.tag];
|
|
4817
|
+
if (!target.enabled) flags.push("disabled");
|
|
4818
|
+
if (target.editable) flags.push("editable");
|
|
4819
|
+
if (target.checked !== void 0) flags.push(target.checked ? "checked" : "unchecked");
|
|
4820
|
+
return flags.join(" ");
|
|
4821
|
+
}
|
|
4822
|
+
function buildSelectorMap(observation) {
|
|
4823
|
+
return Object.fromEntries(observation.targets.map((target) => [target.ref, target.selector]));
|
|
4824
|
+
}
|
|
4825
|
+
function formatObservationCompact(observation, options = {}) {
|
|
4826
|
+
const lines = [
|
|
4827
|
+
`Page: ${observation.title || "(untitled)"}`,
|
|
4828
|
+
`URL: ${observation.url}`,
|
|
4829
|
+
`Screen: ${observation.screenHash}`,
|
|
4830
|
+
""
|
|
4831
|
+
];
|
|
4832
|
+
if (observation.targets.length === 0) {
|
|
4833
|
+
lines.push("(no interactive targets)");
|
|
4834
|
+
} else {
|
|
4835
|
+
for (const target of observation.targets) {
|
|
4836
|
+
lines.push(`@${target.ref} [${targetFlags(target)}]${quoteName(target.name)}`);
|
|
4837
|
+
}
|
|
4838
|
+
}
|
|
4839
|
+
if (options.selectors && observation.targets.length > 0) {
|
|
4840
|
+
lines.push("", "## Selectors");
|
|
4841
|
+
lines.push(observation.targets.map((target) => `${target.ref}: ${target.selector}`).join(" | "));
|
|
4842
|
+
}
|
|
4843
|
+
return lines.join("\n");
|
|
4844
|
+
}
|
|
4845
|
+
async function getPageScreenHash(page) {
|
|
4846
|
+
const observation = await page.evaluate(() => {
|
|
4847
|
+
let h = 2166136261;
|
|
4848
|
+
const input = [location.href, document.title, document.body?.innerText?.slice(0, 5e3) || ""].join("\n");
|
|
4849
|
+
for (let i = 0; i < input.length; i++) {
|
|
4850
|
+
h ^= input.charCodeAt(i);
|
|
4851
|
+
h = Math.imul(h, 16777619);
|
|
4852
|
+
}
|
|
4853
|
+
return (h >>> 0).toString(16);
|
|
4854
|
+
});
|
|
4855
|
+
return observation;
|
|
4856
|
+
}
|
|
4857
|
+
async function actionability(page, selector) {
|
|
4858
|
+
return await page.evaluate((sel) => {
|
|
4859
|
+
const el = document.querySelector(sel);
|
|
4860
|
+
if (!el) return { ok: false, reason: "not_found" };
|
|
4861
|
+
const rect = el.getBoundingClientRect();
|
|
4862
|
+
const style = window.getComputedStyle(el);
|
|
4863
|
+
const visible = rect.width > 0 && rect.height > 0 && style.visibility !== "hidden" && style.display !== "none";
|
|
4864
|
+
if (!visible) return { ok: false, reason: "not_visible" };
|
|
4865
|
+
const input = el;
|
|
4866
|
+
const enabled = !input.disabled && el.getAttribute("aria-disabled") !== "true";
|
|
4867
|
+
if (!enabled) return { ok: false, reason: "disabled" };
|
|
4868
|
+
const cx = rect.left + rect.width / 2;
|
|
4869
|
+
const cy = rect.top + rect.height / 2;
|
|
4870
|
+
const hit = document.elementFromPoint(cx, cy);
|
|
4871
|
+
if (hit && hit !== el && !el.contains(hit) && !hit.contains(el)) {
|
|
4872
|
+
return { ok: false, reason: "covered" };
|
|
4873
|
+
}
|
|
4874
|
+
return { ok: true };
|
|
4875
|
+
}, selector);
|
|
4876
|
+
}
|
|
4877
|
+
async function actOnPage(page, sessionId, input) {
|
|
4878
|
+
const normalizedRef = input.ref ? normalizeAgentRef(input.ref) : void 0;
|
|
4879
|
+
const refMatch = normalizedRef ? getRefTarget(sessionKey(sessionId), normalizedRef) : null;
|
|
4880
|
+
const selector = input.selector || refMatch?.target.selector;
|
|
4881
|
+
if (!selector) {
|
|
4882
|
+
return {
|
|
4883
|
+
action: input.action,
|
|
4884
|
+
selector: "",
|
|
4885
|
+
ref: normalizedRef,
|
|
4886
|
+
success: false,
|
|
4887
|
+
reason: input.ref ? "unknown_ref" : "missing_target",
|
|
4888
|
+
message: normalizedRef ? `Ref "${input.ref}" not found. Run observe again.` : "Provide ref or selector."
|
|
4889
|
+
};
|
|
4890
|
+
}
|
|
4891
|
+
const hash = await getPageScreenHash(page).catch(() => void 0);
|
|
4892
|
+
const stale = !!(refMatch && hash && hash !== refMatch.screenHash);
|
|
4893
|
+
if (!input.force) {
|
|
4894
|
+
const check = await actionability(page, selector);
|
|
4895
|
+
if (!check.ok) {
|
|
4896
|
+
return {
|
|
4897
|
+
action: input.action,
|
|
4898
|
+
selector,
|
|
4899
|
+
ref: normalizedRef,
|
|
4900
|
+
success: false,
|
|
4901
|
+
reason: stale ? "stale_ref" : check.reason,
|
|
4902
|
+
message: stale ? `Ref "${input.ref}" may be stale. Run observe again.` : `Target is not actionable: ${check.reason}`,
|
|
4903
|
+
stale,
|
|
4904
|
+
screenHash: hash,
|
|
4905
|
+
target: refMatch?.target
|
|
4906
|
+
};
|
|
4907
|
+
}
|
|
4908
|
+
}
|
|
4909
|
+
const timeout = input.timeout ?? 1e4;
|
|
4910
|
+
try {
|
|
4911
|
+
switch (input.action) {
|
|
4912
|
+
case "click":
|
|
4913
|
+
await page.locator(selector).first().click({ timeout, force: !!input.force });
|
|
4914
|
+
break;
|
|
4915
|
+
case "fill":
|
|
4916
|
+
if (input.value === void 0) throw new Error("fill requires value");
|
|
4917
|
+
await page.locator(selector).first().fill(input.value, { timeout, force: !!input.force });
|
|
4918
|
+
break;
|
|
4919
|
+
case "type":
|
|
4920
|
+
if (input.value === void 0) throw new Error("type requires value");
|
|
4921
|
+
await page.locator(selector).first().pressSequentially(input.value, { timeout });
|
|
4922
|
+
break;
|
|
4923
|
+
case "press":
|
|
4924
|
+
if (!input.key) throw new Error("press requires key");
|
|
4925
|
+
await page.locator(selector).first().press(input.key, { timeout });
|
|
4926
|
+
break;
|
|
4927
|
+
case "select":
|
|
4928
|
+
if (input.value === void 0) throw new Error("select requires value");
|
|
4929
|
+
await page.locator(selector).first().selectOption(input.value);
|
|
4930
|
+
break;
|
|
4931
|
+
case "check":
|
|
4932
|
+
await page.locator(selector).first().check({ timeout });
|
|
4933
|
+
break;
|
|
4934
|
+
case "hover":
|
|
4935
|
+
await page.locator(selector).first().hover({ timeout });
|
|
4936
|
+
break;
|
|
4937
|
+
default: {
|
|
4938
|
+
const neverAction = input.action;
|
|
4939
|
+
throw new Error(`Unsupported action: ${neverAction}`);
|
|
4940
|
+
}
|
|
4941
|
+
}
|
|
4942
|
+
} catch (error) {
|
|
4943
|
+
return {
|
|
4944
|
+
action: input.action,
|
|
4945
|
+
selector,
|
|
4946
|
+
ref: normalizedRef,
|
|
4947
|
+
success: false,
|
|
4948
|
+
reason: "browser_error",
|
|
4949
|
+
message: error.message,
|
|
4950
|
+
stale,
|
|
4951
|
+
screenHash: hash,
|
|
4952
|
+
target: refMatch?.target
|
|
4953
|
+
};
|
|
4954
|
+
}
|
|
4955
|
+
return {
|
|
4956
|
+
action: input.action,
|
|
4957
|
+
selector,
|
|
4958
|
+
ref: normalizedRef,
|
|
4959
|
+
success: true,
|
|
4960
|
+
stale,
|
|
4961
|
+
screenHash: hash,
|
|
4962
|
+
target: refMatch?.target
|
|
4963
|
+
};
|
|
4964
|
+
}
|
|
4965
|
+
function matchUrlPattern(url, pattern) {
|
|
4966
|
+
if (pattern.includes("*")) {
|
|
4967
|
+
const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
4968
|
+
return new RegExp(`^${escaped}$`).test(url);
|
|
4969
|
+
}
|
|
4970
|
+
return url.includes(pattern);
|
|
4971
|
+
}
|
|
4972
|
+
async function pollUntil(timeout, pollInterval, predicate) {
|
|
4973
|
+
const startedAt = Date.now();
|
|
4974
|
+
while (Date.now() - startedAt <= timeout) {
|
|
4975
|
+
if (await predicate()) return true;
|
|
4976
|
+
await new Promise((resolve16) => setTimeout(resolve16, pollInterval));
|
|
4977
|
+
}
|
|
4978
|
+
return false;
|
|
4979
|
+
}
|
|
4980
|
+
async function waitForPage(page, input) {
|
|
4981
|
+
const timeout = input.timeout ?? 3e4;
|
|
4982
|
+
const pollInterval = input.pollInterval ?? 200;
|
|
4983
|
+
const startedAt = Date.now();
|
|
4984
|
+
try {
|
|
4985
|
+
if (input.selector) {
|
|
4986
|
+
const state = input.state ?? "visible";
|
|
4987
|
+
await page.locator(input.selector).first().waitFor({ state, timeout });
|
|
4988
|
+
return { success: true, matched: "selector", timeout, elapsed: Date.now() - startedAt };
|
|
4989
|
+
}
|
|
4990
|
+
if (input.text) {
|
|
4991
|
+
await page.getByText(input.text).first().waitFor({ state: "visible", timeout });
|
|
4992
|
+
return { success: true, matched: "text", timeout, elapsed: Date.now() - startedAt };
|
|
4993
|
+
}
|
|
4994
|
+
if (input.url) {
|
|
4995
|
+
const matched = await pollUntil(timeout, pollInterval, async () => matchUrlPattern(page.url(), input.url));
|
|
4996
|
+
return {
|
|
4997
|
+
success: matched,
|
|
4998
|
+
matched: "url",
|
|
4999
|
+
timeout,
|
|
5000
|
+
elapsed: Date.now() - startedAt,
|
|
5001
|
+
...matched ? {} : { message: `Timed out waiting for URL pattern: ${input.url}` }
|
|
5002
|
+
};
|
|
5003
|
+
}
|
|
5004
|
+
if (input.load) {
|
|
5005
|
+
await page.waitForLoadState(input.load, timeout);
|
|
5006
|
+
return { success: true, matched: "load", timeout, elapsed: Date.now() - startedAt };
|
|
5007
|
+
}
|
|
5008
|
+
if (input.fn) {
|
|
5009
|
+
await page.waitForFunction(input.fn, void 0, { timeout });
|
|
5010
|
+
return { success: true, matched: "fn", timeout, elapsed: Date.now() - startedAt };
|
|
5011
|
+
}
|
|
5012
|
+
if (input.screenHashChanged) {
|
|
5013
|
+
let screenHash = await getPageScreenHash(page);
|
|
5014
|
+
const matched = await pollUntil(timeout, pollInterval, async () => {
|
|
5015
|
+
screenHash = await getPageScreenHash(page);
|
|
5016
|
+
return screenHash !== input.screenHashChanged;
|
|
5017
|
+
});
|
|
5018
|
+
return {
|
|
5019
|
+
success: matched,
|
|
5020
|
+
matched: "screenHashChanged",
|
|
5021
|
+
timeout,
|
|
5022
|
+
elapsed: Date.now() - startedAt,
|
|
5023
|
+
screenHash,
|
|
5024
|
+
...matched ? {} : { message: `Timed out waiting for screen hash to change: ${input.screenHashChanged}` }
|
|
5025
|
+
};
|
|
5026
|
+
}
|
|
5027
|
+
} catch (error) {
|
|
5028
|
+
return {
|
|
5029
|
+
success: false,
|
|
5030
|
+
matched: input.selector ? "selector" : input.text ? "text" : input.url ? "url" : input.load ? "load" : input.fn ? "fn" : "screenHashChanged",
|
|
5031
|
+
timeout,
|
|
5032
|
+
elapsed: Date.now() - startedAt,
|
|
5033
|
+
message: error.message
|
|
5034
|
+
};
|
|
5035
|
+
}
|
|
5036
|
+
return {
|
|
5037
|
+
success: false,
|
|
5038
|
+
matched: "selector",
|
|
5039
|
+
timeout,
|
|
5040
|
+
elapsed: Date.now() - startedAt,
|
|
5041
|
+
message: "Provide one wait predicate: selector, text, url, load, fn, or screenHashChanged."
|
|
5042
|
+
};
|
|
5043
|
+
}
|
|
5044
|
+
|
|
4616
5045
|
// src/commands/snapshot.ts
|
|
4617
5046
|
var snapshotCommand = registerCommand({
|
|
4618
5047
|
name: "snapshot",
|
|
@@ -4622,7 +5051,14 @@ var snapshotCommand = registerCommand({
|
|
|
4622
5051
|
parameters: z21.object({
|
|
4623
5052
|
type: z21.enum(["aria", "text", "dom", "all"]).default("aria").describe("Snapshot type: aria (accessibility tree), text (visible text), dom (element summary), all (combined)"),
|
|
4624
5053
|
selector: z21.string().optional().describe("Scope to a specific element"),
|
|
4625
|
-
depth: z21.number().optional().default(6).describe("Max depth for DOM/aria tree")
|
|
5054
|
+
depth: z21.number().optional().default(6).describe("Max depth for DOM/aria tree"),
|
|
5055
|
+
interactive: z21.boolean().optional().default(false).describe("Return interactive agent refs only"),
|
|
5056
|
+
interactiveOnly: z21.boolean().optional().default(false).describe("Alias for interactive"),
|
|
5057
|
+
i: z21.boolean().optional().default(false).describe("Short alias for interactive"),
|
|
5058
|
+
compact: z21.boolean().optional().default(false).describe("Include compact agent-browser style snapshot text"),
|
|
5059
|
+
c: z21.boolean().optional().default(false).describe("Short alias for compact"),
|
|
5060
|
+
selectors: z21.boolean().optional().default(false).describe("Include ref to CSS selector map"),
|
|
5061
|
+
all: z21.boolean().optional().default(false).describe("Include hidden interactive targets when using interactive snapshot")
|
|
4626
5062
|
}),
|
|
4627
5063
|
result: z21.object({
|
|
4628
5064
|
url: z21.string(),
|
|
@@ -4635,6 +5071,18 @@ var snapshotCommand = registerCommand({
|
|
|
4635
5071
|
const page = ctx.page;
|
|
4636
5072
|
const url = page.url();
|
|
4637
5073
|
const title = await page.title().catch(() => "");
|
|
5074
|
+
if (p.interactive || p.interactiveOnly || p.i || p.compact || p.c || p.selectors) {
|
|
5075
|
+
const observation = await observePage(page, ctx.sessionId, {
|
|
5076
|
+
includeHidden: p.all
|
|
5077
|
+
});
|
|
5078
|
+
if (p.selectors) observation.selectors = buildSelectorMap(observation);
|
|
5079
|
+
if (p.compact || p.c || p.interactive || p.interactiveOnly || p.i) {
|
|
5080
|
+
observation.compact = formatObservationCompact(observation, { selectors: p.selectors });
|
|
5081
|
+
}
|
|
5082
|
+
return ok20(observation, [
|
|
5083
|
+
`refs refreshed for ${observation.targets.length} targets; use click @e1 or fill @e2 "text"`
|
|
5084
|
+
]);
|
|
5085
|
+
}
|
|
4638
5086
|
if (p.type === "aria") {
|
|
4639
5087
|
const aria = await captureAriaSnapshot(page, p.selector, p.depth);
|
|
4640
5088
|
const tips = await buildRefTips(page, aria);
|
|
@@ -4685,10 +5133,10 @@ async function buildRefTips(page, aria) {
|
|
|
4685
5133
|
return [];
|
|
4686
5134
|
}
|
|
4687
5135
|
}
|
|
4688
|
-
async function captureAriaSnapshot(page, selector,
|
|
5136
|
+
async function captureAriaSnapshot(page, selector, _depth) {
|
|
4689
5137
|
try {
|
|
4690
5138
|
const locator = selector ? page.locator(selector).first() : page.locator("body");
|
|
4691
|
-
return await locator.ariaSnapshot(
|
|
5139
|
+
return await locator.ariaSnapshot();
|
|
4692
5140
|
} catch {
|
|
4693
5141
|
try {
|
|
4694
5142
|
return await page.locator("body").ariaSnapshot();
|
|
@@ -4699,7 +5147,7 @@ async function captureAriaSnapshot(page, selector, depth) {
|
|
|
4699
5147
|
}
|
|
4700
5148
|
async function captureTextSnapshot(page, selector) {
|
|
4701
5149
|
if (selector) {
|
|
4702
|
-
return await page.locator(selector).first().innerText(
|
|
5150
|
+
return await page.locator(selector).first().innerText().catch(() => "");
|
|
4703
5151
|
}
|
|
4704
5152
|
return await page.evaluate(() => document.body?.innerText || "").catch(() => "");
|
|
4705
5153
|
}
|
|
@@ -4735,22 +5183,108 @@ async function captureDomSnapshot(page, selector, maxDepth) {
|
|
|
4735
5183
|
).catch(() => ({ tag: "error" }));
|
|
4736
5184
|
}
|
|
4737
5185
|
|
|
4738
|
-
// src/commands/
|
|
5186
|
+
// src/commands/agent.ts
|
|
4739
5187
|
import { z as z22 } from "zod";
|
|
4740
|
-
import { ok as ok21
|
|
4741
|
-
var
|
|
4742
|
-
|
|
4743
|
-
|
|
4744
|
-
|
|
5188
|
+
import { ok as ok21 } from "@dyyz1993/xcli-core";
|
|
5189
|
+
var observeCommand = registerCommand({
|
|
5190
|
+
name: "observe",
|
|
5191
|
+
description: "Observe the current page as structured agent targets with session refs",
|
|
5192
|
+
scope: "page",
|
|
5193
|
+
parameters: z22.object({
|
|
5194
|
+
includeHidden: z22.boolean().optional().default(false).describe("Include hidden elements in the target list"),
|
|
5195
|
+
limit: z22.number().int().positive().max(300).optional().default(80).describe("Maximum number of targets to return"),
|
|
5196
|
+
compact: z22.boolean().optional().default(false).describe("Include compact agent-browser style snapshot text"),
|
|
5197
|
+
selectors: z22.boolean().optional().default(false).describe("Include ref to stable CSS selector map")
|
|
5198
|
+
}),
|
|
5199
|
+
handler: async (p, ctx) => {
|
|
5200
|
+
const observation = await observePage(ctx.page, ctx.sessionId, {
|
|
5201
|
+
includeHidden: p.includeHidden,
|
|
5202
|
+
limit: p.limit
|
|
5203
|
+
});
|
|
5204
|
+
if (p.selectors) observation.selectors = buildSelectorMap(observation);
|
|
5205
|
+
if (p.compact) observation.compact = formatObservationCompact(observation, { selectors: p.selectors });
|
|
5206
|
+
return ok21(observation, [
|
|
5207
|
+
`refs refreshed for ${observation.targets.length} targets; use act --ref @e1 --action click or click @e1`
|
|
5208
|
+
]);
|
|
5209
|
+
}
|
|
5210
|
+
});
|
|
5211
|
+
var actCommand = registerCommand({
|
|
5212
|
+
name: "act",
|
|
5213
|
+
description: "Perform an agent action using an observe ref or explicit selector",
|
|
5214
|
+
scope: "element",
|
|
5215
|
+
selectorParams: ["selector"],
|
|
5216
|
+
parameters: z22.object({
|
|
5217
|
+
action: z22.enum(["click", "fill", "type", "press", "select", "check", "hover"]).default("click"),
|
|
5218
|
+
ref: z22.string().optional().describe("Session-scoped ref returned by observe, such as e1"),
|
|
5219
|
+
selector: z22.string().optional().describe("CSS selector fallback when no ref is available"),
|
|
5220
|
+
value: z22.string().optional().describe("Value for fill/type/select"),
|
|
5221
|
+
key: z22.string().optional().describe("Key for press"),
|
|
5222
|
+
force: z22.boolean().optional().default(false).describe("Bypass actionability checks"),
|
|
5223
|
+
timeout: z22.number().optional().default(1e4).describe("Playwright action timeout in milliseconds")
|
|
5224
|
+
}).refine((p) => !!p.ref || !!p.selector, {
|
|
5225
|
+
message: "Either ref or selector is required"
|
|
5226
|
+
}),
|
|
5227
|
+
handler: async (p, ctx) => {
|
|
5228
|
+
const result = await actOnPage(ctx.page, ctx.sessionId, { ...p });
|
|
5229
|
+
if (!result.success) {
|
|
5230
|
+
return {
|
|
5231
|
+
success: false,
|
|
5232
|
+
data: result,
|
|
5233
|
+
message: result.message || result.reason || "Action failed",
|
|
5234
|
+
tips: result.stale ? ["run observe again to refresh refs"] : []
|
|
5235
|
+
};
|
|
5236
|
+
}
|
|
5237
|
+
return ok21(result, result.stale ? ["ref screen hash changed; run observe if the next action is uncertain"] : []);
|
|
5238
|
+
}
|
|
5239
|
+
});
|
|
5240
|
+
var waitForCommand = registerCommand({
|
|
5241
|
+
name: "waitFor",
|
|
5242
|
+
description: "Wait for agent predicates such as text, URL, load state, selector state, or screen hash changes",
|
|
5243
|
+
scope: "page",
|
|
5244
|
+
selectorParams: ["selector"],
|
|
5245
|
+
parameters: z22.object({
|
|
5246
|
+
selector: z22.string().optional().describe("CSS selector or observe ref to wait for"),
|
|
5247
|
+
state: z22.enum(["attached", "detached", "visible", "hidden"]).optional().default("visible"),
|
|
5248
|
+
text: z22.string().optional().describe("Visible text to wait for"),
|
|
5249
|
+
url: z22.string().optional().describe("URL substring or glob pattern to wait for"),
|
|
5250
|
+
load: z22.enum(["load", "domcontentloaded", "networkidle"]).optional().describe("Load state to wait for"),
|
|
5251
|
+
fn: z22.string().optional().describe("JavaScript predicate to wait for"),
|
|
5252
|
+
screenHashChanged: z22.string().optional().describe("Previous screenHash from observe"),
|
|
5253
|
+
timeout: z22.number().optional().default(3e4),
|
|
5254
|
+
pollInterval: z22.number().optional().default(200)
|
|
5255
|
+
}).refine((p) => [p.selector, p.text, p.url, p.load, p.fn, p.screenHashChanged].filter(Boolean).length === 1, {
|
|
5256
|
+
message: "Provide exactly one wait predicate: selector, text, url, load, fn, or screenHashChanged"
|
|
5257
|
+
}),
|
|
5258
|
+
handler: async (p, ctx) => {
|
|
5259
|
+
const result = await waitForPage(ctx.page, { ...p });
|
|
5260
|
+
if (!result.success) {
|
|
5261
|
+
return {
|
|
5262
|
+
success: false,
|
|
5263
|
+
data: result,
|
|
5264
|
+
message: result.message || `Timed out waiting for ${result.matched}`,
|
|
5265
|
+
tips: []
|
|
5266
|
+
};
|
|
5267
|
+
}
|
|
5268
|
+
return ok21(result);
|
|
5269
|
+
}
|
|
5270
|
+
});
|
|
5271
|
+
|
|
5272
|
+
// src/commands/tab.ts
|
|
5273
|
+
import { z as z23 } from "zod";
|
|
5274
|
+
import { ok as ok22, fail as fail5 } from "@dyyz1993/xcli-core";
|
|
5275
|
+
var TabParams = z23.object({
|
|
5276
|
+
subcommand: z23.enum(["list", "new", "close", "switch"]),
|
|
5277
|
+
url: z23.string().optional(),
|
|
5278
|
+
index: z23.number().int().min(0).optional()
|
|
4745
5279
|
});
|
|
4746
5280
|
var tabCommand = registerCommand({
|
|
4747
5281
|
name: "tab",
|
|
4748
5282
|
description: "Manage browser tabs: list, new, close, switch",
|
|
4749
5283
|
scope: "page",
|
|
4750
5284
|
parameters: TabParams,
|
|
4751
|
-
result:
|
|
4752
|
-
success:
|
|
4753
|
-
data:
|
|
5285
|
+
result: z23.object({
|
|
5286
|
+
success: z23.boolean(),
|
|
5287
|
+
data: z23.unknown()
|
|
4754
5288
|
}),
|
|
4755
5289
|
handler: async (p, ctx) => {
|
|
4756
5290
|
const pages = ctx.browserContext.pages();
|
|
@@ -4790,7 +5324,7 @@ function handleList(pages, ctx) {
|
|
|
4790
5324
|
active: i === currentIndex
|
|
4791
5325
|
};
|
|
4792
5326
|
});
|
|
4793
|
-
return
|
|
5327
|
+
return ok22({ tabs, total: tabs.length, activeIndex: currentIndex });
|
|
4794
5328
|
}
|
|
4795
5329
|
async function handleNew(p, _pages, ctx) {
|
|
4796
5330
|
const newPage = await ctx.browserContext.newPage();
|
|
@@ -4812,7 +5346,7 @@ async function handleNew(p, _pages, ctx) {
|
|
|
4812
5346
|
const title = await newPage.title().catch(() => "");
|
|
4813
5347
|
const allPages = ctx.browserContext.pages();
|
|
4814
5348
|
const newIndex = allPages.indexOf(newPage);
|
|
4815
|
-
return
|
|
5349
|
+
return ok22({
|
|
4816
5350
|
index: newIndex >= 0 ? newIndex : allPages.length - 1,
|
|
4817
5351
|
url: newPage.url(),
|
|
4818
5352
|
title,
|
|
@@ -4840,7 +5374,7 @@ async function handleClose(p, pages, ctx) {
|
|
|
4840
5374
|
}
|
|
4841
5375
|
ctx.page = newActivePage;
|
|
4842
5376
|
}
|
|
4843
|
-
return
|
|
5377
|
+
return ok22({
|
|
4844
5378
|
closedIndex: closeIndex,
|
|
4845
5379
|
total: remainingPages.length,
|
|
4846
5380
|
activeIndex: isActivePage ? closeIndex < remainingPages.length ? closeIndex : remainingPages.length - 1 : pages.indexOf(ctx.page)
|
|
@@ -4862,7 +5396,7 @@ async function handleSwitch(p, pages, ctx) {
|
|
|
4862
5396
|
}
|
|
4863
5397
|
ctx.page = targetPage;
|
|
4864
5398
|
const title = await targetPage.title().catch(() => "");
|
|
4865
|
-
return
|
|
5399
|
+
return ok22({
|
|
4866
5400
|
index: p.index,
|
|
4867
5401
|
url: targetPage.url(),
|
|
4868
5402
|
title,
|
|
@@ -4871,8 +5405,8 @@ async function handleSwitch(p, pages, ctx) {
|
|
|
4871
5405
|
}
|
|
4872
5406
|
|
|
4873
5407
|
// src/commands/addinitscript.ts
|
|
4874
|
-
import { z as
|
|
4875
|
-
import { ok as
|
|
5408
|
+
import { z as z24 } from "zod";
|
|
5409
|
+
import { ok as ok23 } from "@dyyz1993/xcli-core";
|
|
4876
5410
|
import { readFileSync as readFileSync2 } from "fs";
|
|
4877
5411
|
|
|
4878
5412
|
// src/chain-parser.ts
|
|
@@ -5050,7 +5584,7 @@ function parseCommandArgs(name, args) {
|
|
|
5050
5584
|
} else {
|
|
5051
5585
|
if (positionalIndex < positionalKeys.length) {
|
|
5052
5586
|
const isLast = positionalIndex === positionalKeys.length - 1;
|
|
5053
|
-
if (isLast && name === "eval") {
|
|
5587
|
+
if (isLast && (name === "eval" || name === "find")) {
|
|
5054
5588
|
const remaining = args.slice(i).map(unquote2).join(" ");
|
|
5055
5589
|
params[positionalKeys[positionalIndex]] = remaining;
|
|
5056
5590
|
break;
|
|
@@ -5100,7 +5634,7 @@ var commandDefCache = {
|
|
|
5100
5634
|
frames: { positional: [] },
|
|
5101
5635
|
frame: { positional: ["selector"] },
|
|
5102
5636
|
actions: { positional: ["url"] },
|
|
5103
|
-
find: { positional: ["strategy", "value"] },
|
|
5637
|
+
find: { positional: ["strategy", "value", "operation"] },
|
|
5104
5638
|
addinitscript: { positional: ["script"] },
|
|
5105
5639
|
tab: { positional: ["subcommand"] }
|
|
5106
5640
|
};
|
|
@@ -5112,14 +5646,14 @@ function registerCommandDefinition(name, positional) {
|
|
|
5112
5646
|
}
|
|
5113
5647
|
|
|
5114
5648
|
// src/commands/addinitscript.ts
|
|
5115
|
-
var InitScriptParams =
|
|
5116
|
-
script:
|
|
5117
|
-
file:
|
|
5118
|
-
stdin:
|
|
5119
|
-
name:
|
|
5120
|
-
list:
|
|
5121
|
-
remove:
|
|
5122
|
-
base64:
|
|
5649
|
+
var InitScriptParams = z24.object({
|
|
5650
|
+
script: z24.string().optional(),
|
|
5651
|
+
file: z24.string().optional(),
|
|
5652
|
+
stdin: z24.boolean().optional(),
|
|
5653
|
+
name: z24.string().optional(),
|
|
5654
|
+
list: z24.boolean().optional(),
|
|
5655
|
+
remove: z24.string().optional(),
|
|
5656
|
+
base64: z24.string().optional()
|
|
5123
5657
|
});
|
|
5124
5658
|
var registeredScripts = /* @__PURE__ */ new Map();
|
|
5125
5659
|
function resolveScriptContent(params) {
|
|
@@ -5160,15 +5694,15 @@ var addInitScriptCommand = registerCommand({
|
|
|
5160
5694
|
size: content2.length,
|
|
5161
5695
|
preview: content2.slice(0, 80)
|
|
5162
5696
|
}));
|
|
5163
|
-
return
|
|
5697
|
+
return ok23({ scripts });
|
|
5164
5698
|
}
|
|
5165
5699
|
if (params.remove) {
|
|
5166
5700
|
const existed = registeredScripts.delete(params.remove);
|
|
5167
|
-
return
|
|
5701
|
+
return ok23({ removed: params.remove, existed });
|
|
5168
5702
|
}
|
|
5169
5703
|
let content = params.stdin ? await readStdin() : resolveScriptContent(params);
|
|
5170
5704
|
if (!content) {
|
|
5171
|
-
return
|
|
5705
|
+
return ok23({ error: "No script content provided. Use --script, --file, --stdin, or --base64" });
|
|
5172
5706
|
}
|
|
5173
5707
|
const scriptName = params.name ?? `script-${Date.now()}`;
|
|
5174
5708
|
registeredScripts.set(scriptName, content);
|
|
@@ -5176,74 +5710,134 @@ var addInitScriptCommand = registerCommand({
|
|
|
5176
5710
|
try {
|
|
5177
5711
|
await ctx.page.evaluate(content);
|
|
5178
5712
|
} catch {
|
|
5179
|
-
return
|
|
5713
|
+
return ok23({
|
|
5180
5714
|
registered: scriptName,
|
|
5181
5715
|
hint: "Script registered for future page loads; immediate execution skipped (page may not be ready)"
|
|
5182
5716
|
});
|
|
5183
5717
|
}
|
|
5184
|
-
return
|
|
5718
|
+
return ok23({ registered: scriptName, executedImmediately: true });
|
|
5185
5719
|
}
|
|
5186
5720
|
});
|
|
5187
5721
|
registerCommandDefinition("addinitscript", ["script"]);
|
|
5188
5722
|
|
|
5189
5723
|
// src/commands/find.ts
|
|
5190
|
-
import { z as
|
|
5191
|
-
import { ok as
|
|
5724
|
+
import { z as z25 } from "zod";
|
|
5725
|
+
import { ok as ok24, fail as fail6 } from "@dyyz1993/xcli-core";
|
|
5726
|
+
var actionSchema2 = z25.enum(["click", "fill", "type", "select", "hover", "check"]);
|
|
5192
5727
|
var findCommand = registerCommand({
|
|
5193
5728
|
name: "find",
|
|
5194
5729
|
description: "Find elements by semantic strategy (text/role/label/placeholder/testid) and optionally perform an action",
|
|
5195
5730
|
scope: "page",
|
|
5196
|
-
parameters:
|
|
5197
|
-
strategy:
|
|
5198
|
-
value:
|
|
5199
|
-
name:
|
|
5200
|
-
exact:
|
|
5201
|
-
|
|
5202
|
-
|
|
5203
|
-
|
|
5204
|
-
|
|
5205
|
-
|
|
5731
|
+
parameters: z25.object({
|
|
5732
|
+
strategy: z25.enum(["text", "role", "label", "placeholder", "testid", "alt", "title", "first", "last", "nth"]),
|
|
5733
|
+
value: z25.string(),
|
|
5734
|
+
name: z25.string().optional(),
|
|
5735
|
+
exact: z25.boolean().optional().default(false),
|
|
5736
|
+
operation: z25.string().optional().describe('Trailing operation syntax, e.g. click, fill "text", type "text"'),
|
|
5737
|
+
action: actionSchema2.optional().describe("Action to perform when not using trailing operation syntax"),
|
|
5738
|
+
actionValue: z25.string().optional().describe("Value for fill/type/select when using action"),
|
|
5739
|
+
index: z25.number().int().optional().describe("Index for nth strategy"),
|
|
5740
|
+
click: z25.boolean().optional().default(false),
|
|
5741
|
+
fill: z25.string().optional(),
|
|
5742
|
+
type: z25.string().optional(),
|
|
5743
|
+
select: z25.string().optional(),
|
|
5744
|
+
hover: z25.boolean().optional().default(false),
|
|
5745
|
+
check: z25.boolean().optional().default(false),
|
|
5746
|
+
timeout: z25.number().optional().default(1e4)
|
|
5206
5747
|
}),
|
|
5207
|
-
result:
|
|
5208
|
-
matched:
|
|
5209
|
-
selector:
|
|
5210
|
-
action:
|
|
5748
|
+
result: z25.object({
|
|
5749
|
+
matched: z25.number(),
|
|
5750
|
+
selector: z25.string(),
|
|
5751
|
+
action: z25.string().optional()
|
|
5211
5752
|
}),
|
|
5212
5753
|
handler: async (p, ctx) => {
|
|
5213
5754
|
const page = ctx.page;
|
|
5214
|
-
const
|
|
5755
|
+
const normalized = normalizeFindParams({ ...p });
|
|
5756
|
+
const parsedOperation = parseOperation(normalized.operation);
|
|
5757
|
+
const actionName = parsedOperation.action || p.action || inferLegacyAction(p);
|
|
5758
|
+
const actionValue = parsedOperation.value ?? p.actionValue ?? p.fill ?? p.type ?? p.select;
|
|
5759
|
+
const locator = buildLocator(page, normalized.strategy, normalized.value, {
|
|
5215
5760
|
name: p.name,
|
|
5216
|
-
exact: p.exact
|
|
5761
|
+
exact: p.exact,
|
|
5762
|
+
index: normalized.index
|
|
5217
5763
|
});
|
|
5218
5764
|
const count = await locator.count();
|
|
5219
5765
|
if (count === 0) {
|
|
5220
5766
|
return fail6(`No element found with ${p.strategy}="${p.value}"`);
|
|
5221
5767
|
}
|
|
5222
5768
|
const tips = [];
|
|
5223
|
-
const target = locator.
|
|
5769
|
+
const target = selectTarget(locator, p.strategy);
|
|
5224
5770
|
if (count > 1) {
|
|
5225
|
-
tips.push(`\u26A0\uFE0F Matched ${count} elements,
|
|
5771
|
+
tips.push(`\u26A0\uFE0F Matched ${count} elements, used first match. Use 'find nth <index> ${normalized.strategy} "${normalized.value}" ${actionName || "click"}' for a specific match.`);
|
|
5226
5772
|
}
|
|
5227
|
-
const selector = describeSelector(
|
|
5228
|
-
|
|
5229
|
-
if (p.click) {
|
|
5773
|
+
const selector = describeSelector(normalized.strategy, normalized.value, p.name);
|
|
5774
|
+
if (actionName === "click") {
|
|
5230
5775
|
await target.click({ timeout: p.timeout, force: true });
|
|
5231
|
-
action
|
|
5232
|
-
} else if (
|
|
5233
|
-
|
|
5234
|
-
|
|
5235
|
-
|
|
5236
|
-
|
|
5237
|
-
|
|
5238
|
-
|
|
5239
|
-
|
|
5240
|
-
|
|
5241
|
-
|
|
5242
|
-
|
|
5243
|
-
|
|
5244
|
-
|
|
5776
|
+
return okWithTips({ matched: count, selector, action: "click" }, tips);
|
|
5777
|
+
} else if (actionName === "fill") {
|
|
5778
|
+
if (actionValue === void 0) return fail6("find fill requires a value");
|
|
5779
|
+
await target.fill(actionValue, { timeout: p.timeout, force: true });
|
|
5780
|
+
return okWithTips({ matched: count, selector, action: `fill("${actionValue}")` }, tips);
|
|
5781
|
+
} else if (actionName === "type") {
|
|
5782
|
+
if (actionValue === void 0) return fail6("find type requires a value");
|
|
5783
|
+
await target.type(actionValue, { delay: 10, timeout: p.timeout });
|
|
5784
|
+
return okWithTips({ matched: count, selector, action: `type("${actionValue}")` }, tips);
|
|
5785
|
+
} else if (actionName === "select") {
|
|
5786
|
+
if (actionValue === void 0) return fail6("find select requires a value");
|
|
5787
|
+
await target.selectOption(actionValue);
|
|
5788
|
+
return okWithTips({ matched: count, selector, action: `select("${actionValue}")` }, tips);
|
|
5789
|
+
} else if (actionName === "hover") {
|
|
5790
|
+
await target.hover({ timeout: p.timeout, force: true });
|
|
5791
|
+
return okWithTips({ matched: count, selector, action: "hover" }, tips);
|
|
5792
|
+
} else if (actionName === "check") {
|
|
5793
|
+
await target.check({ timeout: p.timeout });
|
|
5794
|
+
return okWithTips({ matched: count, selector, action: "check" }, tips);
|
|
5795
|
+
}
|
|
5796
|
+
return okWithTips({ matched: count, selector }, tips);
|
|
5245
5797
|
}
|
|
5246
5798
|
});
|
|
5799
|
+
function okWithTips(data, tips) {
|
|
5800
|
+
const result = ok24(data);
|
|
5801
|
+
if (tips.length > 0) result.tips = tips;
|
|
5802
|
+
return result;
|
|
5803
|
+
}
|
|
5804
|
+
function parseOperation(operation) {
|
|
5805
|
+
if (!operation) return {};
|
|
5806
|
+
const match = operation.trim().match(/^(\S+)(?:\s+([\s\S]+))?$/);
|
|
5807
|
+
if (!match) return {};
|
|
5808
|
+
const maybeAction = match[1];
|
|
5809
|
+
const parsed = actionSchema2.safeParse(maybeAction);
|
|
5810
|
+
if (!parsed.success) return {};
|
|
5811
|
+
const rawValue = match[2];
|
|
5812
|
+
const value = rawValue?.replace(/^["']|["']$/g, "");
|
|
5813
|
+
return { action: parsed.data, ...value !== void 0 ? { value } : {} };
|
|
5814
|
+
}
|
|
5815
|
+
function normalizeFindParams(p) {
|
|
5816
|
+
if (p.strategy !== "nth") return { strategy: p.strategy, value: p.value, operation: p.operation, index: p.index };
|
|
5817
|
+
const parsedIndex = Number(p.value);
|
|
5818
|
+
if (!Number.isInteger(parsedIndex) || !p.operation) {
|
|
5819
|
+
return { strategy: p.strategy, value: p.value, operation: p.operation, index: p.index };
|
|
5820
|
+
}
|
|
5821
|
+
const match = p.operation.trim().match(/^(\S+)(?:\s+([\s\S]+))?$/);
|
|
5822
|
+
if (!match) {
|
|
5823
|
+
return { strategy: p.strategy, value: p.value, operation: p.operation, index: parsedIndex };
|
|
5824
|
+
}
|
|
5825
|
+
return {
|
|
5826
|
+
strategy: p.strategy,
|
|
5827
|
+
value: match[1].replace(/^["']|["']$/g, ""),
|
|
5828
|
+
...match[2] ? { operation: match[2] } : {},
|
|
5829
|
+
index: parsedIndex
|
|
5830
|
+
};
|
|
5831
|
+
}
|
|
5832
|
+
function inferLegacyAction(p) {
|
|
5833
|
+
if (p.click) return "click";
|
|
5834
|
+
if (p.fill !== void 0) return "fill";
|
|
5835
|
+
if (p.type !== void 0) return "type";
|
|
5836
|
+
if (p.select !== void 0) return "select";
|
|
5837
|
+
if (p.hover) return "hover";
|
|
5838
|
+
if (p.check) return "check";
|
|
5839
|
+
return void 0;
|
|
5840
|
+
}
|
|
5247
5841
|
function buildLocator(page, strategy, value, opts) {
|
|
5248
5842
|
switch (strategy) {
|
|
5249
5843
|
case "text":
|
|
@@ -5256,10 +5850,24 @@ function buildLocator(page, strategy, value, opts) {
|
|
|
5256
5850
|
return page.getByPlaceholder(value, { exact: opts.exact });
|
|
5257
5851
|
case "testid":
|
|
5258
5852
|
return page.getByTestId(value);
|
|
5853
|
+
case "alt":
|
|
5854
|
+
return page.getByAltText(value, { exact: opts.exact });
|
|
5855
|
+
case "title":
|
|
5856
|
+
return page.getByTitle(value, { exact: opts.exact });
|
|
5857
|
+
case "first":
|
|
5858
|
+
return page.locator(value).first();
|
|
5859
|
+
case "last":
|
|
5860
|
+
return page.locator(value).last();
|
|
5861
|
+
case "nth":
|
|
5862
|
+
return page.locator(value).nth(opts.index ?? 0);
|
|
5259
5863
|
default:
|
|
5260
5864
|
return page.getByText(value, { exact: opts.exact });
|
|
5261
5865
|
}
|
|
5262
5866
|
}
|
|
5867
|
+
function selectTarget(locator, strategy) {
|
|
5868
|
+
if (strategy === "first" || strategy === "last" || strategy === "nth") return locator;
|
|
5869
|
+
return locator.first();
|
|
5870
|
+
}
|
|
5263
5871
|
function describeSelector(strategy, value, name) {
|
|
5264
5872
|
switch (strategy) {
|
|
5265
5873
|
case "role":
|
|
@@ -5272,6 +5880,16 @@ function describeSelector(strategy, value, name) {
|
|
|
5272
5880
|
return `getByPlaceholder("${value}")`;
|
|
5273
5881
|
case "testid":
|
|
5274
5882
|
return `getByTestId("${value}")`;
|
|
5883
|
+
case "alt":
|
|
5884
|
+
return `getByAltText("${value}")`;
|
|
5885
|
+
case "title":
|
|
5886
|
+
return `getByTitle("${value}")`;
|
|
5887
|
+
case "first":
|
|
5888
|
+
return `first("${value}")`;
|
|
5889
|
+
case "last":
|
|
5890
|
+
return `last("${value}")`;
|
|
5891
|
+
case "nth":
|
|
5892
|
+
return `nth("${value}")`;
|
|
5275
5893
|
default:
|
|
5276
5894
|
return `${strategy}("${value}")`;
|
|
5277
5895
|
}
|
|
@@ -5394,7 +6012,7 @@ async function detectCaptcha2(page) {
|
|
|
5394
6012
|
}
|
|
5395
6013
|
async function detectWarningText(page) {
|
|
5396
6014
|
try {
|
|
5397
|
-
const pageText = await page.textContent("body"
|
|
6015
|
+
const pageText = await page.textContent("body").catch(() => "") || "";
|
|
5398
6016
|
const lowerText = pageText.toLowerCase();
|
|
5399
6017
|
for (const { text, severity } of WARNING_TEXTS) {
|
|
5400
6018
|
if (lowerText.includes(text.toLowerCase())) {
|
|
@@ -5525,8 +6143,8 @@ function formatDetectionMessage(result) {
|
|
|
5525
6143
|
}
|
|
5526
6144
|
|
|
5527
6145
|
// src/commands/promo.ts
|
|
5528
|
-
import { z as
|
|
5529
|
-
import { ok as
|
|
6146
|
+
import { z as z26 } from "zod";
|
|
6147
|
+
import { ok as ok25 } from "@dyyz1993/xcli-core";
|
|
5530
6148
|
import { existsSync as existsSync2, readFileSync as readFileSync8 } from "fs";
|
|
5531
6149
|
import { resolve as resolve6 } from "path";
|
|
5532
6150
|
|
|
@@ -5829,14 +6447,14 @@ async function dispatchPromo(config) {
|
|
|
5829
6447
|
}
|
|
5830
6448
|
|
|
5831
6449
|
// src/commands/promo.ts
|
|
5832
|
-
var promoParams =
|
|
5833
|
-
platform:
|
|
5834
|
-
file:
|
|
5835
|
-
tags:
|
|
5836
|
-
title:
|
|
5837
|
-
search:
|
|
5838
|
-
cdpEndpoint:
|
|
5839
|
-
session:
|
|
6450
|
+
var promoParams = z26.object({
|
|
6451
|
+
platform: z26.enum(["devto", "medium", "csdn", "juejin", "quora"]).describe("Target platform for promotion"),
|
|
6452
|
+
file: z26.string().describe("Path to Markdown file to publish"),
|
|
6453
|
+
tags: z26.string().optional().describe("Comma-separated tags"),
|
|
6454
|
+
title: z26.string().optional().describe("Custom title (default: extracted from file first heading)"),
|
|
6455
|
+
search: z26.string().optional().describe("Quora: search query to find questions"),
|
|
6456
|
+
cdpEndpoint: z26.string().optional().describe("CDP endpoint for agent-browser"),
|
|
6457
|
+
session: z26.string().optional().describe("agent-browser session name")
|
|
5840
6458
|
}).refine(
|
|
5841
6459
|
(data) => data.platform !== "quora" || !!data.search,
|
|
5842
6460
|
{ message: "Quora platform requires --search parameter" }
|
|
@@ -5849,17 +6467,17 @@ var promoCommand = registerCommand({
|
|
|
5849
6467
|
description: "Publish promotional articles to various platforms (devto, medium, csdn, juejin, quora)",
|
|
5850
6468
|
scope: "project",
|
|
5851
6469
|
parameters: promoParams,
|
|
5852
|
-
result:
|
|
5853
|
-
success:
|
|
5854
|
-
url:
|
|
5855
|
-
error:
|
|
5856
|
-
platform:
|
|
6470
|
+
result: z26.object({
|
|
6471
|
+
success: z26.boolean(),
|
|
6472
|
+
url: z26.string().optional(),
|
|
6473
|
+
error: z26.string().optional(),
|
|
6474
|
+
platform: z26.string()
|
|
5857
6475
|
}),
|
|
5858
6476
|
handler: async (p, _ctx) => {
|
|
5859
6477
|
const filePath = resolve6(p.file);
|
|
5860
6478
|
const content = readFileSync8(filePath, "utf-8");
|
|
5861
6479
|
if (content.trim().length === 0) {
|
|
5862
|
-
return
|
|
6480
|
+
return ok25({
|
|
5863
6481
|
success: false,
|
|
5864
6482
|
error: `File is empty: ${filePath}`,
|
|
5865
6483
|
platform: p.platform
|
|
@@ -5874,7 +6492,7 @@ var promoCommand = registerCommand({
|
|
|
5874
6492
|
cdpEndpoint: p.cdpEndpoint ?? _ctx.cdpEndpoint,
|
|
5875
6493
|
session: p.session ?? p.platform
|
|
5876
6494
|
});
|
|
5877
|
-
return
|
|
6495
|
+
return ok25(result);
|
|
5878
6496
|
}
|
|
5879
6497
|
});
|
|
5880
6498
|
|
|
@@ -5962,7 +6580,7 @@ import { join as join2 } from "path";
|
|
|
5962
6580
|
import { execSync as execSync6 } from "child_process";
|
|
5963
6581
|
var SHARED_PLUGIN_DEPENDENCIES = {
|
|
5964
6582
|
"zod": "^3.24.0",
|
|
5965
|
-
"@dyyz1993/xcli-core": "^0.
|
|
6583
|
+
"@dyyz1993/xcli-core": "^0.12.1"
|
|
5966
6584
|
};
|
|
5967
6585
|
function ensurePluginDependencies(pluginsDir) {
|
|
5968
6586
|
const zodPath = join2(pluginsDir, "node_modules", "zod");
|
|
@@ -6001,6 +6619,192 @@ function ensurePluginDependencies(pluginsDir) {
|
|
|
6001
6619
|
}
|
|
6002
6620
|
}
|
|
6003
6621
|
|
|
6622
|
+
// src/plugin/contract.ts
|
|
6623
|
+
function buildPluginContract(site) {
|
|
6624
|
+
const commands = site.getAllCommands().map((command) => buildCommandContract(site.getCommand?.(command.name) || command));
|
|
6625
|
+
return {
|
|
6626
|
+
version: 2,
|
|
6627
|
+
plugin: {
|
|
6628
|
+
name: site.name,
|
|
6629
|
+
url: site.url,
|
|
6630
|
+
description: site.config?.description,
|
|
6631
|
+
requiresLogin: site.config?.requiresLogin
|
|
6632
|
+
},
|
|
6633
|
+
commands
|
|
6634
|
+
};
|
|
6635
|
+
}
|
|
6636
|
+
function buildCommandContract(command) {
|
|
6637
|
+
const extension = command.xbrowser || {};
|
|
6638
|
+
const inferredFields = fieldsFromZodObject(command.parameters);
|
|
6639
|
+
const fields = mergeFields(inferredFields, extension.form?.fields || []);
|
|
6640
|
+
const positional = extension.positional || fields.filter((field) => field.positional).map((field) => field.name);
|
|
6641
|
+
const capabilities = extension.capabilities || inferCapabilities(command.scope || "project", command.requiresLogin);
|
|
6642
|
+
const outputSchema = command.result ? summarizeZod(command.result) : void 0;
|
|
6643
|
+
return {
|
|
6644
|
+
name: command.name,
|
|
6645
|
+
description: command.description || "",
|
|
6646
|
+
scope: command.scope || "project",
|
|
6647
|
+
requiresLogin: command.requiresLogin === true,
|
|
6648
|
+
category: extension.category,
|
|
6649
|
+
capabilities,
|
|
6650
|
+
positional,
|
|
6651
|
+
form: {
|
|
6652
|
+
title: extension.form?.title || command.description || command.name,
|
|
6653
|
+
description: extension.form?.description,
|
|
6654
|
+
submitLabel: extension.form?.submitLabel || "Run",
|
|
6655
|
+
fields
|
|
6656
|
+
},
|
|
6657
|
+
output: extension.output || (outputSchema ? { schema: outputSchema } : void 0)
|
|
6658
|
+
};
|
|
6659
|
+
}
|
|
6660
|
+
function fieldsFromZodObject(schema) {
|
|
6661
|
+
const shape = getShape(schema);
|
|
6662
|
+
if (!shape) return [];
|
|
6663
|
+
return Object.entries(shape).map(([name, field]) => fieldFromZod(name, field));
|
|
6664
|
+
}
|
|
6665
|
+
function fieldFromZod(name, schema) {
|
|
6666
|
+
const unwrapped = unwrapZod(schema);
|
|
6667
|
+
const type = zodTypeToContractType(unwrapped.typeName);
|
|
6668
|
+
const enumValues = extractEnumValues(unwrapped.schema);
|
|
6669
|
+
return {
|
|
6670
|
+
name,
|
|
6671
|
+
label: toLabel(name),
|
|
6672
|
+
type,
|
|
6673
|
+
widget: widgetFor(type, enumValues, unwrapped.schema),
|
|
6674
|
+
required: !unwrapped.optional,
|
|
6675
|
+
...unwrapped.description ? { description: unwrapped.description } : {},
|
|
6676
|
+
...unwrapped.defaultValue !== void 0 ? { default: unwrapped.defaultValue } : {},
|
|
6677
|
+
...enumValues ? { enum: enumValues } : {},
|
|
6678
|
+
...type === "array" ? { multiple: true } : {}
|
|
6679
|
+
};
|
|
6680
|
+
}
|
|
6681
|
+
function mergeFields(inferred, overrides) {
|
|
6682
|
+
if (overrides.length === 0) return inferred;
|
|
6683
|
+
const byName = new Map(inferred.map((field) => [field.name, field]));
|
|
6684
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6685
|
+
const merged = [];
|
|
6686
|
+
for (const override of overrides) {
|
|
6687
|
+
if (!override.name) continue;
|
|
6688
|
+
const base = byName.get(override.name) || {
|
|
6689
|
+
name: override.name,
|
|
6690
|
+
label: toLabel(override.name),
|
|
6691
|
+
type: "string",
|
|
6692
|
+
widget: "text",
|
|
6693
|
+
required: false
|
|
6694
|
+
};
|
|
6695
|
+
merged.push({ ...base, ...override, name: override.name });
|
|
6696
|
+
seen.add(override.name);
|
|
6697
|
+
}
|
|
6698
|
+
for (const field of inferred) {
|
|
6699
|
+
if (!seen.has(field.name)) merged.push(field);
|
|
6700
|
+
}
|
|
6701
|
+
return merged;
|
|
6702
|
+
}
|
|
6703
|
+
function inferCapabilities(scope, requiresLogin) {
|
|
6704
|
+
const caps = [];
|
|
6705
|
+
if (scope === "page") caps.push("browser.page");
|
|
6706
|
+
if (scope === "browser") caps.push("browser.context");
|
|
6707
|
+
if (requiresLogin) caps.push("auth.login");
|
|
6708
|
+
return caps;
|
|
6709
|
+
}
|
|
6710
|
+
function getShape(schema) {
|
|
6711
|
+
const zod = schema;
|
|
6712
|
+
const shapeOrFn = zod?.shape ?? zod?._def?.shape;
|
|
6713
|
+
if (!shapeOrFn) return void 0;
|
|
6714
|
+
return typeof shapeOrFn === "function" ? shapeOrFn() : shapeOrFn;
|
|
6715
|
+
}
|
|
6716
|
+
function unwrapZod(schema) {
|
|
6717
|
+
let current = schema;
|
|
6718
|
+
let optional = typeof current?.isOptional === "function" ? current.isOptional() : false;
|
|
6719
|
+
let description = current?._def?.description;
|
|
6720
|
+
let defaultValue;
|
|
6721
|
+
for (let i = 0; i < 8; i++) {
|
|
6722
|
+
const def = current?._def;
|
|
6723
|
+
const typeName = def?.typeName || "unknown";
|
|
6724
|
+
if (def?.description) description = def.description;
|
|
6725
|
+
if (!def) return { schema: current, typeName, optional, description, defaultValue };
|
|
6726
|
+
if (typeName === "ZodDefault") {
|
|
6727
|
+
optional = true;
|
|
6728
|
+
defaultValue = typeof def.defaultValue === "function" ? def.defaultValue() : def.defaultValue;
|
|
6729
|
+
current = def.innerType || def.type;
|
|
6730
|
+
continue;
|
|
6731
|
+
}
|
|
6732
|
+
if (typeName === "ZodOptional" || typeName === "ZodNullable") {
|
|
6733
|
+
optional = true;
|
|
6734
|
+
current = def.innerType || def.type;
|
|
6735
|
+
continue;
|
|
6736
|
+
}
|
|
6737
|
+
return { schema: current, typeName, optional, description, defaultValue };
|
|
6738
|
+
}
|
|
6739
|
+
return { schema: current, typeName: current?._def?.typeName || "unknown", optional, description, defaultValue };
|
|
6740
|
+
}
|
|
6741
|
+
function zodTypeToContractType(typeName) {
|
|
6742
|
+
switch (typeName) {
|
|
6743
|
+
case "ZodString":
|
|
6744
|
+
return "string";
|
|
6745
|
+
case "ZodNumber":
|
|
6746
|
+
return "number";
|
|
6747
|
+
case "ZodBoolean":
|
|
6748
|
+
return "boolean";
|
|
6749
|
+
case "ZodEnum":
|
|
6750
|
+
case "ZodNativeEnum":
|
|
6751
|
+
return "enum";
|
|
6752
|
+
case "ZodArray":
|
|
6753
|
+
return "array";
|
|
6754
|
+
case "ZodObject":
|
|
6755
|
+
return "object";
|
|
6756
|
+
default:
|
|
6757
|
+
return typeName.replace(/^Zod/, "").toLowerCase() || "unknown";
|
|
6758
|
+
}
|
|
6759
|
+
}
|
|
6760
|
+
function widgetFor(type, enumValues, schema) {
|
|
6761
|
+
if (enumValues) return "select";
|
|
6762
|
+
if (type === "boolean") return "checkbox";
|
|
6763
|
+
if (type === "number") return "number";
|
|
6764
|
+
if (type === "array") return "multi-select";
|
|
6765
|
+
if (type === "object") return "json";
|
|
6766
|
+
const checks = schema?._def?.checks;
|
|
6767
|
+
if (checks?.some((check) => check.kind === "url")) return "url";
|
|
6768
|
+
return "text";
|
|
6769
|
+
}
|
|
6770
|
+
function extractEnumValues(schema) {
|
|
6771
|
+
const def = schema?._def;
|
|
6772
|
+
const values = def?.values;
|
|
6773
|
+
if (Array.isArray(values)) return values.map(String);
|
|
6774
|
+
return void 0;
|
|
6775
|
+
}
|
|
6776
|
+
function summarizeZod(schema) {
|
|
6777
|
+
const unwrapped = unwrapZod(schema);
|
|
6778
|
+
if (unwrapped.typeName === "ZodArray") {
|
|
6779
|
+
const def = unwrapped.schema?._def;
|
|
6780
|
+
return {
|
|
6781
|
+
type: "array",
|
|
6782
|
+
items: summarizeZod(def?.type || def?.innerType)
|
|
6783
|
+
};
|
|
6784
|
+
}
|
|
6785
|
+
const shape = getShape(schema);
|
|
6786
|
+
if (!shape) {
|
|
6787
|
+
return {
|
|
6788
|
+
type: zodTypeToContractType(unwrapped.typeName),
|
|
6789
|
+
required: !unwrapped.optional,
|
|
6790
|
+
...unwrapped.description ? { description: unwrapped.description } : {}
|
|
6791
|
+
};
|
|
6792
|
+
}
|
|
6793
|
+
return Object.fromEntries(
|
|
6794
|
+
Object.entries(shape).map(([name, field]) => {
|
|
6795
|
+
const unwrapped2 = unwrapZod(field);
|
|
6796
|
+
return [name, {
|
|
6797
|
+
type: zodTypeToContractType(unwrapped2.typeName),
|
|
6798
|
+
required: !unwrapped2.optional,
|
|
6799
|
+
...unwrapped2.description ? { description: unwrapped2.description } : {}
|
|
6800
|
+
}];
|
|
6801
|
+
})
|
|
6802
|
+
);
|
|
6803
|
+
}
|
|
6804
|
+
function toLabel(name) {
|
|
6805
|
+
return name.replace(/([A-Z])/g, " $1").replace(/[-_]+/g, " ").replace(/\s+/g, " ").trim().replace(/^./, (char) => char.toUpperCase());
|
|
6806
|
+
}
|
|
6807
|
+
|
|
6004
6808
|
// src/plugin/loader.ts
|
|
6005
6809
|
var DEFAULT_PLUGIN_DIRS = [".xcli/plugins", "../.xcli/plugins"];
|
|
6006
6810
|
var XBrowserPluginLoader = class {
|
|
@@ -6043,6 +6847,13 @@ var XBrowserPluginLoader = class {
|
|
|
6043
6847
|
getLoadedPlugins() {
|
|
6044
6848
|
return this.loader.getLoadedPlugins();
|
|
6045
6849
|
}
|
|
6850
|
+
getPluginContract(siteName, commandName) {
|
|
6851
|
+
const site = this.core.loader.getSite(siteName);
|
|
6852
|
+
if (!site) return void 0;
|
|
6853
|
+
const contract = buildPluginContract(site);
|
|
6854
|
+
if (!commandName) return contract;
|
|
6855
|
+
return contract.commands.find((command) => command.name === commandName);
|
|
6856
|
+
}
|
|
6046
6857
|
async loadPlugin(pluginPath, id) {
|
|
6047
6858
|
return this.loader.loadPlugin(pluginPath, id);
|
|
6048
6859
|
}
|
|
@@ -6546,45 +7357,13 @@ async function loadHooks() {
|
|
|
6546
7357
|
// src/executor.ts
|
|
6547
7358
|
import { homedir as homedir3 } from "os";
|
|
6548
7359
|
import { join as join3 } from "path";
|
|
6549
|
-
import { existsSync as existsSync6, readFileSync as readFileSync9, writeFileSync as writeFileSync4, mkdirSync as mkdirSync3 } from "fs";
|
|
6550
7360
|
var NAVIGATION_COMMANDS = /* @__PURE__ */ new Set(["goto", "back", "forward", "refresh"]);
|
|
6551
7361
|
var snapshotHintShown = /* @__PURE__ */ new WeakSet();
|
|
6552
7362
|
var STORAGE_DIR = join3(homedir3(), ".xbrowser", "storage");
|
|
6553
7363
|
var storageCache = /* @__PURE__ */ new Map();
|
|
6554
7364
|
function getPluginStorage(pluginName) {
|
|
6555
7365
|
if (!storageCache.has(pluginName)) {
|
|
6556
|
-
|
|
6557
|
-
let data = {};
|
|
6558
|
-
const load3 = () => {
|
|
6559
|
-
if (existsSync6(filePath)) {
|
|
6560
|
-
try {
|
|
6561
|
-
data = JSON.parse(readFileSync9(filePath, "utf-8"));
|
|
6562
|
-
} catch {
|
|
6563
|
-
data = {};
|
|
6564
|
-
}
|
|
6565
|
-
}
|
|
6566
|
-
};
|
|
6567
|
-
const save = () => {
|
|
6568
|
-
mkdirSync3(STORAGE_DIR, { recursive: true });
|
|
6569
|
-
writeFileSync4(filePath, JSON.stringify(data, null, 2), "utf-8");
|
|
6570
|
-
};
|
|
6571
|
-
load3();
|
|
6572
|
-
storageCache.set(pluginName, {
|
|
6573
|
-
get: async (key) => data[key] ?? null,
|
|
6574
|
-
set: async (key, value) => {
|
|
6575
|
-
data[key] = value;
|
|
6576
|
-
save();
|
|
6577
|
-
},
|
|
6578
|
-
delete: async (key) => {
|
|
6579
|
-
delete data[key];
|
|
6580
|
-
save();
|
|
6581
|
-
},
|
|
6582
|
-
clear: async () => {
|
|
6583
|
-
data = {};
|
|
6584
|
-
save();
|
|
6585
|
-
},
|
|
6586
|
-
keys: async () => Object.keys(data)
|
|
6587
|
-
});
|
|
7366
|
+
storageCache.set(pluginName, new PluginStorage(pluginName, STORAGE_DIR));
|
|
6588
7367
|
}
|
|
6589
7368
|
return storageCache.get(pluginName);
|
|
6590
7369
|
}
|
|
@@ -6648,7 +7427,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
6648
7427
|
}
|
|
6649
7428
|
let targetPageOverride = null;
|
|
6650
7429
|
if (_target && extraOpts?.cdpEndpoint) {
|
|
6651
|
-
const { findTargetPage } = await import("./browser-
|
|
7430
|
+
const { findTargetPage } = await import("./browser-53KUFEEM.js");
|
|
6652
7431
|
targetPageOverride = await findTargetPage(extraOpts.cdpEndpoint, _target);
|
|
6653
7432
|
if (!targetPageOverride) {
|
|
6654
7433
|
return errorResult(`Target "${_target}" not found. Use 'xbrowser targets --cdp ${extraOpts.cdpEndpoint}' to list available pages.`);
|
|
@@ -6665,7 +7444,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
6665
7444
|
params = result.data;
|
|
6666
7445
|
}
|
|
6667
7446
|
if (command.scope !== "cli" && !process.env.XBROWSER_DAEMON_WORKER) {
|
|
6668
|
-
const { forwardExec } = await import("./daemon-client-
|
|
7447
|
+
const { forwardExec } = await import("./daemon-client-YAVQ343A.js");
|
|
6669
7448
|
const result = await forwardExec(commandName, params, sessionName, extraOpts?.cdpEndpoint);
|
|
6670
7449
|
if (result) return result;
|
|
6671
7450
|
}
|
|
@@ -6673,7 +7452,18 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
6673
7452
|
const existing = await findOrRestoreSession(sessionName, extraOpts?.cdpEndpoint);
|
|
6674
7453
|
if (existing) {
|
|
6675
7454
|
session = existing;
|
|
6676
|
-
if (
|
|
7455
|
+
if (session.page) {
|
|
7456
|
+
try {
|
|
7457
|
+
await Promise.race([
|
|
7458
|
+
session.page.evaluate(() => true),
|
|
7459
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error("timeout")), 3e3))
|
|
7460
|
+
]);
|
|
7461
|
+
} catch {
|
|
7462
|
+
await closeSessionByName(session.name);
|
|
7463
|
+
session = void 0;
|
|
7464
|
+
}
|
|
7465
|
+
}
|
|
7466
|
+
if (session && targetPageOverride && session.page) {
|
|
6677
7467
|
const currentUrl = session.page.url();
|
|
6678
7468
|
if (currentUrl !== targetPageOverride.url) {
|
|
6679
7469
|
await session.page.goto(targetPageOverride.url, { waitUntil: "domcontentloaded", timeout: 15e3 }).catch(() => {
|
|
@@ -6684,6 +7474,10 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
6684
7474
|
session = await createSession(sessionName, params.url, {
|
|
6685
7475
|
cdpEndpoint: extraOpts?.cdpEndpoint
|
|
6686
7476
|
});
|
|
7477
|
+
} else if (command.scope === "browser") {
|
|
7478
|
+
session = await createSession(sessionName, void 0, {
|
|
7479
|
+
cdpEndpoint: extraOpts?.cdpEndpoint
|
|
7480
|
+
});
|
|
6687
7481
|
} else if (command.scope !== "project") {
|
|
6688
7482
|
return errorResult(
|
|
6689
7483
|
`Session '${sessionName}' not found. Run "xbrowser session open <url>" first.`
|
|
@@ -6694,7 +7488,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
6694
7488
|
browser: session?.context.browser(),
|
|
6695
7489
|
browserContext: session?.context,
|
|
6696
7490
|
sessionId: session?.id,
|
|
6697
|
-
cdpEndpoint:
|
|
7491
|
+
cdpEndpoint: session?.cdpEndpoint || extraOpts?.cdpEndpoint,
|
|
6698
7492
|
args: [],
|
|
6699
7493
|
options: {},
|
|
6700
7494
|
cwd: process.cwd(),
|
|
@@ -6729,7 +7523,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
6729
7523
|
let refTips = [];
|
|
6730
7524
|
if (session?.page && command.selectorParams && command.selectorParams.length > 0) {
|
|
6731
7525
|
const cache = /* @__PURE__ */ new Map();
|
|
6732
|
-
const resolved = await resolveRefParams(session.page, params, command.selectorParams, cache);
|
|
7526
|
+
const resolved = await resolveRefParams(session.page, params, command.selectorParams, cache, session.id);
|
|
6733
7527
|
if (resolved.tips.length > 0) {
|
|
6734
7528
|
refTips = resolved.tips;
|
|
6735
7529
|
params = resolved.params;
|
|
@@ -6799,7 +7593,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
6799
7593
|
timestamp: start
|
|
6800
7594
|
});
|
|
6801
7595
|
if (isSuccess) {
|
|
6802
|
-
return { ...
|
|
7596
|
+
return { ...ok26(raw.data, merged.length > 0 ? merged : raw.tips), duration, ...hookOutputs ? { hookOutputs } : {} };
|
|
6803
7597
|
}
|
|
6804
7598
|
return { success: false, data: raw.data, message: raw.message, tips: merged.length > 0 ? merged : raw.tips || [], duration, ...hookOutputs ? { hookOutputs } : {} };
|
|
6805
7599
|
}
|
|
@@ -6812,7 +7606,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
6812
7606
|
duration,
|
|
6813
7607
|
timestamp: start
|
|
6814
7608
|
});
|
|
6815
|
-
return { ...
|
|
7609
|
+
return { ...ok26(raw, smartTips), duration, ...hookOutputs ? { hookOutputs } : {} };
|
|
6816
7610
|
} catch (err) {
|
|
6817
7611
|
const end = Date.now();
|
|
6818
7612
|
const duration = end - start;
|
|
@@ -6960,7 +7754,7 @@ async function executeChain(input, options) {
|
|
|
6960
7754
|
results.push({
|
|
6961
7755
|
command: `${cmdName} ${subCommand}`,
|
|
6962
7756
|
raw: cmdStr,
|
|
6963
|
-
...
|
|
7757
|
+
...ok26(data),
|
|
6964
7758
|
duration: duration2,
|
|
6965
7759
|
...hookOutputs ? { hookOutputs } : {}
|
|
6966
7760
|
});
|
|
@@ -7235,13 +8029,13 @@ var sessionListBuiltin = {
|
|
|
7235
8029
|
},
|
|
7236
8030
|
execute: async () => {
|
|
7237
8031
|
try {
|
|
7238
|
-
const
|
|
7239
|
-
if (
|
|
8032
|
+
const sessions2 = await listSessions();
|
|
8033
|
+
if (sessions2.length === 0) {
|
|
7240
8034
|
console.log("No active sessions");
|
|
7241
8035
|
return;
|
|
7242
8036
|
}
|
|
7243
8037
|
console.log("Active sessions:");
|
|
7244
|
-
for (const s of
|
|
8038
|
+
for (const s of sessions2) {
|
|
7245
8039
|
console.log(` ${s.name} (${s.id})`);
|
|
7246
8040
|
}
|
|
7247
8041
|
} catch (e) {
|
|
@@ -7333,26 +8127,26 @@ var configBuiltin = {
|
|
|
7333
8127
|
|
|
7334
8128
|
// src/plugin/installer.ts
|
|
7335
8129
|
import {
|
|
7336
|
-
existsSync as
|
|
8130
|
+
existsSync as existsSync12,
|
|
7337
8131
|
readdirSync as readdirSync3,
|
|
7338
|
-
mkdirSync as
|
|
8132
|
+
mkdirSync as mkdirSync7,
|
|
7339
8133
|
rmSync as rmSync7
|
|
7340
8134
|
} from "fs";
|
|
7341
8135
|
import { resolve as resolve15, basename as basename2 } from "path";
|
|
7342
8136
|
import { homedir as homedir4 } from "os";
|
|
7343
8137
|
|
|
7344
8138
|
// src/plugin/install-sources/local.ts
|
|
7345
|
-
import { existsSync as
|
|
8139
|
+
import { existsSync as existsSync7, cpSync as cpSync2, rmSync as rmSync2 } from "fs";
|
|
7346
8140
|
import { resolve as resolve10 } from "path";
|
|
7347
8141
|
|
|
7348
8142
|
// src/plugin/install-utils.ts
|
|
7349
8143
|
import {
|
|
7350
|
-
existsSync as
|
|
8144
|
+
existsSync as existsSync6,
|
|
7351
8145
|
readdirSync as readdirSync2,
|
|
7352
8146
|
cpSync,
|
|
7353
8147
|
rmSync,
|
|
7354
|
-
mkdirSync as
|
|
7355
|
-
readFileSync as
|
|
8148
|
+
mkdirSync as mkdirSync3,
|
|
8149
|
+
readFileSync as readFileSync9,
|
|
7356
8150
|
createWriteStream
|
|
7357
8151
|
} from "fs";
|
|
7358
8152
|
import { resolve as resolve9 } from "path";
|
|
@@ -7423,7 +8217,7 @@ async function downloadToFile(url, destPath) {
|
|
|
7423
8217
|
await pipeline(nodeStream, createWriteStream(destPath));
|
|
7424
8218
|
}
|
|
7425
8219
|
function extractTarGz(tarballPath, targetDir) {
|
|
7426
|
-
|
|
8220
|
+
mkdirSync3(targetDir, { recursive: true });
|
|
7427
8221
|
execSync7(`tar -xzf "${tarballPath}" -C "${targetDir}"`, { stdio: "pipe" });
|
|
7428
8222
|
}
|
|
7429
8223
|
function flattenPackageRoot(targetDir) {
|
|
@@ -7444,18 +8238,18 @@ function flattenPackageRoot(targetDir) {
|
|
|
7444
8238
|
async function verifyPlugin(dir) {
|
|
7445
8239
|
const warnings = [];
|
|
7446
8240
|
const indexPath = resolve9(dir, "index.ts");
|
|
7447
|
-
if (!
|
|
8241
|
+
if (!existsSync6(indexPath)) {
|
|
7448
8242
|
const indexJs = resolve9(dir, "index.js");
|
|
7449
|
-
if (!
|
|
8243
|
+
if (!existsSync6(indexJs)) {
|
|
7450
8244
|
return { valid: false, error: "No index.ts or index.js entry point found", warnings };
|
|
7451
8245
|
}
|
|
7452
8246
|
}
|
|
7453
8247
|
const pkgPath = resolve9(dir, "package.json");
|
|
7454
|
-
if (!
|
|
8248
|
+
if (!existsSync6(pkgPath)) {
|
|
7455
8249
|
warnings.push("No package.json found");
|
|
7456
8250
|
} else {
|
|
7457
8251
|
try {
|
|
7458
|
-
const pkg2 = JSON.parse(
|
|
8252
|
+
const pkg2 = JSON.parse(readFileSync9(pkgPath, "utf-8"));
|
|
7459
8253
|
if (!pkg2.xbrowser) {
|
|
7460
8254
|
warnings.push("No xbrowser metadata in package.json");
|
|
7461
8255
|
}
|
|
@@ -7475,7 +8269,7 @@ function safeCleanup(dir) {
|
|
|
7475
8269
|
// src/plugin/install-sources/local.ts
|
|
7476
8270
|
async function installFromLocal(source, name, targetDir) {
|
|
7477
8271
|
const srcPath = resolve10(source);
|
|
7478
|
-
if (!
|
|
8272
|
+
if (!existsSync7(srcPath)) {
|
|
7479
8273
|
throw new Error(`Local path does not exist: ${srcPath}`);
|
|
7480
8274
|
}
|
|
7481
8275
|
const tmpTarget = `${targetDir}-tmp-${Date.now()}`;
|
|
@@ -7488,7 +8282,7 @@ async function installFromLocal(source, name, targetDir) {
|
|
|
7488
8282
|
safeCleanup(tmpTarget);
|
|
7489
8283
|
throw new Error(`Invalid plugin: ${verify.error}`);
|
|
7490
8284
|
}
|
|
7491
|
-
if (
|
|
8285
|
+
if (existsSync7(targetDir)) {
|
|
7492
8286
|
rmSync2(targetDir, { recursive: true, force: true });
|
|
7493
8287
|
}
|
|
7494
8288
|
cpSync2(tmpTarget, targetDir, { recursive: true, force: true });
|
|
@@ -7508,7 +8302,7 @@ async function installFromLocal(source, name, targetDir) {
|
|
|
7508
8302
|
}
|
|
7509
8303
|
|
|
7510
8304
|
// src/plugin/install-sources/npm.ts
|
|
7511
|
-
import { existsSync as
|
|
8305
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync10, writeFileSync as writeFileSync4, rmSync as rmSync3, cpSync as cpSync3 } from "fs";
|
|
7512
8306
|
import { resolve as resolve11, join as join4 } from "path";
|
|
7513
8307
|
import { tmpdir } from "os";
|
|
7514
8308
|
async function installFromNpm(packageName, name, targetDir) {
|
|
@@ -7529,7 +8323,7 @@ async function installFromNpm(packageName, name, targetDir) {
|
|
|
7529
8323
|
}
|
|
7530
8324
|
const tarballUrl = versionMeta.dist.tarball;
|
|
7531
8325
|
const tmpDir = join4(tmpdir(), `xbrowser-npm-${Date.now()}`);
|
|
7532
|
-
|
|
8326
|
+
mkdirSync4(tmpDir, { recursive: true });
|
|
7533
8327
|
let warnings = [];
|
|
7534
8328
|
try {
|
|
7535
8329
|
const tarballPath = join4(tmpDir, `${name}.tgz`);
|
|
@@ -7542,16 +8336,16 @@ async function installFromNpm(packageName, name, targetDir) {
|
|
|
7542
8336
|
if (!verify.valid) {
|
|
7543
8337
|
throw new Error(`Invalid npm plugin: ${verify.error}`);
|
|
7544
8338
|
}
|
|
7545
|
-
if (
|
|
8339
|
+
if (existsSync8(targetDir)) {
|
|
7546
8340
|
rmSync3(targetDir, { recursive: true, force: true });
|
|
7547
8341
|
}
|
|
7548
8342
|
cpSync3(extractDir, targetDir, { recursive: true, force: true });
|
|
7549
8343
|
const pkgPath = resolve11(targetDir, "package.json");
|
|
7550
|
-
if (
|
|
7551
|
-
const pkg2 = JSON.parse(
|
|
8344
|
+
if (existsSync8(pkgPath)) {
|
|
8345
|
+
const pkg2 = JSON.parse(readFileSync10(pkgPath, "utf-8"));
|
|
7552
8346
|
if (!pkg2._npmSource) {
|
|
7553
8347
|
pkg2._npmSource = { name: packageName, version: latestVersion };
|
|
7554
|
-
|
|
8348
|
+
writeFileSync4(pkgPath, JSON.stringify(pkg2, null, 2));
|
|
7555
8349
|
}
|
|
7556
8350
|
}
|
|
7557
8351
|
} finally {
|
|
@@ -7568,7 +8362,7 @@ async function installFromNpm(packageName, name, targetDir) {
|
|
|
7568
8362
|
}
|
|
7569
8363
|
|
|
7570
8364
|
// src/plugin/install-sources/git.ts
|
|
7571
|
-
import { existsSync as
|
|
8365
|
+
import { existsSync as existsSync9, readFileSync as readFileSync11, writeFileSync as writeFileSync5, rmSync as rmSync4, cpSync as cpSync4 } from "fs";
|
|
7572
8366
|
import { resolve as resolve12, join as join5 } from "path";
|
|
7573
8367
|
import { tmpdir as tmpdir2 } from "os";
|
|
7574
8368
|
import { execSync as execSync8 } from "child_process";
|
|
@@ -7582,17 +8376,17 @@ async function installFromGit(gitUrl, name, targetDir) {
|
|
|
7582
8376
|
if (!verify.valid) {
|
|
7583
8377
|
throw new Error(`Invalid git plugin: ${verify.error}`);
|
|
7584
8378
|
}
|
|
7585
|
-
if (
|
|
8379
|
+
if (existsSync9(targetDir)) {
|
|
7586
8380
|
rmSync4(targetDir, { recursive: true, force: true });
|
|
7587
8381
|
}
|
|
7588
8382
|
cpSync4(tmpDir, targetDir, { recursive: true, force: true });
|
|
7589
8383
|
rmSync4(resolve12(targetDir, ".git"), { recursive: true, force: true });
|
|
7590
8384
|
const pkgPath = resolve12(targetDir, "package.json");
|
|
7591
|
-
if (
|
|
7592
|
-
const pkg2 = JSON.parse(
|
|
8385
|
+
if (existsSync9(pkgPath)) {
|
|
8386
|
+
const pkg2 = JSON.parse(readFileSync11(pkgPath, "utf-8"));
|
|
7593
8387
|
if (!pkg2._gitSource) {
|
|
7594
8388
|
pkg2._gitSource = { url: gitUrl };
|
|
7595
|
-
|
|
8389
|
+
writeFileSync5(pkgPath, JSON.stringify(pkg2, null, 2));
|
|
7596
8390
|
}
|
|
7597
8391
|
}
|
|
7598
8392
|
} finally {
|
|
@@ -7609,12 +8403,12 @@ async function installFromGit(gitUrl, name, targetDir) {
|
|
|
7609
8403
|
}
|
|
7610
8404
|
|
|
7611
8405
|
// src/plugin/install-sources/url.ts
|
|
7612
|
-
import { existsSync as
|
|
8406
|
+
import { existsSync as existsSync10, readFileSync as readFileSync12, writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, rmSync as rmSync5, cpSync as cpSync5 } from "fs";
|
|
7613
8407
|
import { resolve as resolve13, join as join6, basename } from "path";
|
|
7614
8408
|
import { tmpdir as tmpdir3 } from "os";
|
|
7615
8409
|
async function installFromUrl(url, name, targetDir) {
|
|
7616
8410
|
const tmpDir = join6(tmpdir3(), `xbrowser-url-${Date.now()}`);
|
|
7617
|
-
|
|
8411
|
+
mkdirSync5(tmpDir, { recursive: true });
|
|
7618
8412
|
let warnings = [];
|
|
7619
8413
|
try {
|
|
7620
8414
|
const fileName = basename(new URL(url).pathname) || "plugin.tar.gz";
|
|
@@ -7628,16 +8422,16 @@ async function installFromUrl(url, name, targetDir) {
|
|
|
7628
8422
|
if (!verify.valid) {
|
|
7629
8423
|
throw new Error(`Invalid plugin from URL: ${verify.error}`);
|
|
7630
8424
|
}
|
|
7631
|
-
if (
|
|
8425
|
+
if (existsSync10(targetDir)) {
|
|
7632
8426
|
rmSync5(targetDir, { recursive: true, force: true });
|
|
7633
8427
|
}
|
|
7634
8428
|
cpSync5(extractDir, targetDir, { recursive: true, force: true });
|
|
7635
8429
|
const pkgPath = resolve13(targetDir, "package.json");
|
|
7636
|
-
if (
|
|
7637
|
-
const pkg2 = JSON.parse(
|
|
8430
|
+
if (existsSync10(pkgPath)) {
|
|
8431
|
+
const pkg2 = JSON.parse(readFileSync12(pkgPath, "utf-8"));
|
|
7638
8432
|
if (!pkg2._urlSource) {
|
|
7639
8433
|
pkg2._urlSource = { url };
|
|
7640
|
-
|
|
8434
|
+
writeFileSync6(pkgPath, JSON.stringify(pkg2, null, 2));
|
|
7641
8435
|
}
|
|
7642
8436
|
}
|
|
7643
8437
|
} finally {
|
|
@@ -7655,10 +8449,10 @@ async function installFromUrl(url, name, targetDir) {
|
|
|
7655
8449
|
|
|
7656
8450
|
// src/plugin/install-sources/marketplace.ts
|
|
7657
8451
|
import {
|
|
7658
|
-
existsSync as
|
|
7659
|
-
mkdirSync as
|
|
7660
|
-
writeFileSync as
|
|
7661
|
-
readFileSync as
|
|
8452
|
+
existsSync as existsSync11,
|
|
8453
|
+
mkdirSync as mkdirSync6,
|
|
8454
|
+
writeFileSync as writeFileSync7,
|
|
8455
|
+
readFileSync as readFileSync13,
|
|
7662
8456
|
rmSync as rmSync6,
|
|
7663
8457
|
cpSync as cpSync6
|
|
7664
8458
|
} from "fs";
|
|
@@ -7680,12 +8474,12 @@ async function installFromMarketplace(pluginsDir, slug, options) {
|
|
|
7680
8474
|
const plugin = detailData.data;
|
|
7681
8475
|
const name = options?.name || String(plugin.slug || slug);
|
|
7682
8476
|
const targetDir = resolve14(pluginsDir, name);
|
|
7683
|
-
if (
|
|
8477
|
+
if (existsSync11(targetDir) && !options?.force) {
|
|
7684
8478
|
throw new Error(`Plugin "${name}" already exists. Use --force to overwrite.`);
|
|
7685
8479
|
}
|
|
7686
|
-
|
|
8480
|
+
mkdirSync6(targetDir, { recursive: true });
|
|
7687
8481
|
const tmpDir = join7(tmpdir4(), `xbrowser-marketplace-${Date.now()}`);
|
|
7688
|
-
|
|
8482
|
+
mkdirSync6(tmpDir, { recursive: true });
|
|
7689
8483
|
const realSlug = String(plugin.slug || slug);
|
|
7690
8484
|
try {
|
|
7691
8485
|
await downloadAndExtractMarketplaceTarball(baseUrl, realSlug, tmpDir, targetDir);
|
|
@@ -7715,14 +8509,14 @@ function isManifestArray(data) {
|
|
|
7715
8509
|
return Array.isArray(data) && data.length > 0 && typeof data[0].path === "string" && typeof data[0].content === "string";
|
|
7716
8510
|
}
|
|
7717
8511
|
function extractManifestToDir(manifest, targetDir) {
|
|
7718
|
-
if (
|
|
8512
|
+
if (existsSync11(targetDir)) {
|
|
7719
8513
|
rmSync6(targetDir, { recursive: true, force: true });
|
|
7720
8514
|
}
|
|
7721
|
-
|
|
8515
|
+
mkdirSync6(targetDir, { recursive: true });
|
|
7722
8516
|
for (const file of manifest) {
|
|
7723
8517
|
const filePath = resolve14(targetDir, file.path);
|
|
7724
|
-
|
|
7725
|
-
|
|
8518
|
+
mkdirSync6(dirname2(filePath), { recursive: true });
|
|
8519
|
+
writeFileSync7(filePath, Buffer.from(file.content, "base64"));
|
|
7726
8520
|
}
|
|
7727
8521
|
}
|
|
7728
8522
|
function tryParseAsGzippedManifest(buffer) {
|
|
@@ -7749,7 +8543,7 @@ async function downloadAndExtractMarketplaceTarball(baseUrl, slug, tmpDir, targe
|
|
|
7749
8543
|
const redirectUrl = tarballRes.headers.get("location");
|
|
7750
8544
|
const tarballPath = join7(tmpDir, `${slug}.tar.gz`);
|
|
7751
8545
|
await downloadToFile(redirectUrl, tarballPath);
|
|
7752
|
-
const buffer =
|
|
8546
|
+
const buffer = readFileSync13(tarballPath);
|
|
7753
8547
|
const manifest = tryParseAsGzippedManifest(buffer);
|
|
7754
8548
|
if (manifest) {
|
|
7755
8549
|
extractManifestToDir(manifest, targetDir);
|
|
@@ -7758,7 +8552,7 @@ async function downloadAndExtractMarketplaceTarball(baseUrl, slug, tmpDir, targe
|
|
|
7758
8552
|
const extractDir = join7(tmpDir, "extracted");
|
|
7759
8553
|
extractTarGz(tarballPath, extractDir);
|
|
7760
8554
|
flattenPackageRoot(extractDir);
|
|
7761
|
-
if (
|
|
8555
|
+
if (existsSync11(targetDir)) {
|
|
7762
8556
|
rmSync6(targetDir, { recursive: true, force: true });
|
|
7763
8557
|
}
|
|
7764
8558
|
cpSync6(extractDir, targetDir, { recursive: true, force: true });
|
|
@@ -7770,12 +8564,12 @@ async function downloadAndExtractMarketplaceTarball(baseUrl, slug, tmpDir, targe
|
|
|
7770
8564
|
return;
|
|
7771
8565
|
}
|
|
7772
8566
|
const tarballPath = join7(tmpDir, `${slug}.tar.gz`);
|
|
7773
|
-
|
|
8567
|
+
writeFileSync7(tarballPath, buffer);
|
|
7774
8568
|
try {
|
|
7775
8569
|
const extractDir = join7(tmpDir, "extracted");
|
|
7776
8570
|
extractTarGz(tarballPath, extractDir);
|
|
7777
8571
|
flattenPackageRoot(extractDir);
|
|
7778
|
-
if (
|
|
8572
|
+
if (existsSync11(targetDir)) {
|
|
7779
8573
|
rmSync6(targetDir, { recursive: true, force: true });
|
|
7780
8574
|
}
|
|
7781
8575
|
cpSync6(extractDir, targetDir, { recursive: true, force: true });
|
|
@@ -7811,24 +8605,24 @@ function writeMarketplacePackageJson(plugin, slug, name, baseUrl, targetDir) {
|
|
|
7811
8605
|
}
|
|
7812
8606
|
};
|
|
7813
8607
|
const pkgPath = resolve14(targetDir, "package.json");
|
|
7814
|
-
if (!
|
|
7815
|
-
|
|
8608
|
+
if (!existsSync11(pkgPath)) {
|
|
8609
|
+
writeFileSync7(pkgPath, JSON.stringify(packageJson, null, 2));
|
|
7816
8610
|
} else {
|
|
7817
8611
|
try {
|
|
7818
|
-
const existing = JSON.parse(
|
|
8612
|
+
const existing = JSON.parse(readFileSync13(pkgPath, "utf-8"));
|
|
7819
8613
|
const merged = {
|
|
7820
8614
|
...existing,
|
|
7821
8615
|
xbrowser: { ...existing.xbrowser, ...packageJson.xbrowser },
|
|
7822
8616
|
_marketplace: packageJson._marketplace
|
|
7823
8617
|
};
|
|
7824
|
-
|
|
8618
|
+
writeFileSync7(pkgPath, JSON.stringify(merged, null, 2));
|
|
7825
8619
|
} catch {
|
|
7826
|
-
|
|
8620
|
+
writeFileSync7(pkgPath, JSON.stringify(packageJson, null, 2));
|
|
7827
8621
|
}
|
|
7828
8622
|
}
|
|
7829
8623
|
}
|
|
7830
8624
|
function ensureIndexFile(plugin, name, targetDir) {
|
|
7831
|
-
if (!
|
|
8625
|
+
if (!existsSync11(resolve14(targetDir, "index.ts")) && !existsSync11(resolve14(targetDir, "index.js"))) {
|
|
7832
8626
|
const commands = plugin.commands || [];
|
|
7833
8627
|
const commandHandlers = commands.length > 0 ? commands.map((cmd) => {
|
|
7834
8628
|
return [
|
|
@@ -7843,7 +8637,7 @@ function ensureIndexFile(plugin, name, targetDir) {
|
|
|
7843
8637
|
` handler: async () => ({ data: { message: 'Hello from ${name}!' }, tips: [] }),`,
|
|
7844
8638
|
` });`
|
|
7845
8639
|
].join("\n");
|
|
7846
|
-
|
|
8640
|
+
writeFileSync7(
|
|
7847
8641
|
resolve14(targetDir, "index.ts"),
|
|
7848
8642
|
[
|
|
7849
8643
|
`import type { XCLIAPI } from '@dyyz1993/xcli-core';`,
|
|
@@ -7882,10 +8676,10 @@ var PluginInstaller = class {
|
|
|
7882
8676
|
const type = this.detectSourceType(source);
|
|
7883
8677
|
const name = options?.name || this.deriveName(source, type);
|
|
7884
8678
|
const targetDir = resolve15(this.pluginsDir, name);
|
|
7885
|
-
if (
|
|
8679
|
+
if (existsSync12(targetDir) && !options?.force) {
|
|
7886
8680
|
throw new Error(`Plugin "${name}" already exists. Use --force to overwrite.`);
|
|
7887
8681
|
}
|
|
7888
|
-
|
|
8682
|
+
mkdirSync7(targetDir, { recursive: true });
|
|
7889
8683
|
const resolvedSource = type === "npm" ? await resolveNpmPackageWithFallback(source) : source;
|
|
7890
8684
|
switch (type) {
|
|
7891
8685
|
case "local":
|
|
@@ -7943,7 +8737,7 @@ var PluginInstaller = class {
|
|
|
7943
8737
|
*/
|
|
7944
8738
|
async uninstall(name) {
|
|
7945
8739
|
const targetDir = resolve15(this.pluginsDir, name);
|
|
7946
|
-
if (!
|
|
8740
|
+
if (!existsSync12(targetDir)) {
|
|
7947
8741
|
throw new Error(`Plugin "${name}" not found`);
|
|
7948
8742
|
}
|
|
7949
8743
|
rmSync7(targetDir, { recursive: true, force: true });
|
|
@@ -7954,7 +8748,7 @@ var PluginInstaller = class {
|
|
|
7954
8748
|
* @returns Array of installed plugin information.
|
|
7955
8749
|
*/
|
|
7956
8750
|
async list(_options) {
|
|
7957
|
-
if (!
|
|
8751
|
+
if (!existsSync12(this.pluginsDir)) return [];
|
|
7958
8752
|
const entries = readdirSync3(this.pluginsDir, { withFileTypes: true });
|
|
7959
8753
|
const plugins = [];
|
|
7960
8754
|
for (const entry of entries) {
|
|
@@ -7962,7 +8756,7 @@ var PluginInstaller = class {
|
|
|
7962
8756
|
const pluginPath = resolve15(this.pluginsDir, entry.name);
|
|
7963
8757
|
const indexPath = resolve15(pluginPath, "index.ts");
|
|
7964
8758
|
const indexJsPath = resolve15(pluginPath, "index.js");
|
|
7965
|
-
if (!
|
|
8759
|
+
if (!existsSync12(indexPath) && !existsSync12(indexJsPath)) continue;
|
|
7966
8760
|
const metadata = PluginMetadataParser.parseFromPackageJson(pluginPath);
|
|
7967
8761
|
let source = "local";
|
|
7968
8762
|
const pkg2 = readJsonFile(resolve15(pluginPath, "package.json"), {});
|
|
@@ -7989,10 +8783,10 @@ var PluginInstaller = class {
|
|
|
7989
8783
|
}
|
|
7990
8784
|
if (source.startsWith("file://")) {
|
|
7991
8785
|
const filePath = decodeURIComponent(new URL(source).pathname);
|
|
7992
|
-
if (
|
|
8786
|
+
if (existsSync12(filePath)) return "url";
|
|
7993
8787
|
}
|
|
7994
8788
|
if (source.endsWith(".git") || source.includes("github.com/")) return "git";
|
|
7995
|
-
if (
|
|
8789
|
+
if (existsSync12(resolve15(source))) return "local";
|
|
7996
8790
|
return "npm";
|
|
7997
8791
|
}
|
|
7998
8792
|
deriveName(source, type) {
|
|
@@ -8067,6 +8861,7 @@ function handlePluginHelp() {
|
|
|
8067
8861
|
" install <slug> --from-marketplace Install from marketplace",
|
|
8068
8862
|
" uninstall <name> Uninstall a plugin",
|
|
8069
8863
|
" list [--json] List installed plugins",
|
|
8864
|
+
" schema <name> [command] [--json] Show plugin contract and command forms",
|
|
8070
8865
|
" reload <name> Reload a plugin",
|
|
8071
8866
|
"",
|
|
8072
8867
|
"Examples:",
|
|
@@ -8075,6 +8870,7 @@ function handlePluginHelp() {
|
|
|
8075
8870
|
" xbrowser plugin install ./my-plugin",
|
|
8076
8871
|
" xbrowser plugin uninstall my-plugin",
|
|
8077
8872
|
" xbrowser plugin list",
|
|
8873
|
+
" xbrowser plugin schema my-plugin --json",
|
|
8078
8874
|
" xbrowser plugin reload my-plugin"
|
|
8079
8875
|
].join("\n");
|
|
8080
8876
|
}
|
|
@@ -9137,6 +9933,25 @@ async function handleBrowserCommand(command, args, options, sessionName, mode, c
|
|
|
9137
9933
|
cmdName = "text";
|
|
9138
9934
|
params = { selector: options.selector || options.s };
|
|
9139
9935
|
break;
|
|
9936
|
+
case "find": {
|
|
9937
|
+
if (!args[0] || !args[1]) {
|
|
9938
|
+
outputError("Usage: xbrowser find <text|role|label|placeholder|testid|alt|title|first|last|nth> <value> [action] [--name <name>]");
|
|
9939
|
+
}
|
|
9940
|
+
const strategy = args[0];
|
|
9941
|
+
const value = args[1];
|
|
9942
|
+
const operation = args.slice(2).join(" ") || void 0;
|
|
9943
|
+
cmdName = "find";
|
|
9944
|
+
params = {
|
|
9945
|
+
strategy,
|
|
9946
|
+
value,
|
|
9947
|
+
...operation ? { operation } : {},
|
|
9948
|
+
name: options.name,
|
|
9949
|
+
exact: !!options.exact,
|
|
9950
|
+
timeout: options.timeout ? Number(options.timeout) : void 0,
|
|
9951
|
+
index: options.index ? Number(options.index) : void 0
|
|
9952
|
+
};
|
|
9953
|
+
break;
|
|
9954
|
+
}
|
|
9140
9955
|
case "back":
|
|
9141
9956
|
cmdName = "back";
|
|
9142
9957
|
params = {};
|
|
@@ -9328,11 +10143,11 @@ async function handleSession(args, options, mode, cdpEndpoint) {
|
|
|
9328
10143
|
case "list":
|
|
9329
10144
|
case "ls": {
|
|
9330
10145
|
try {
|
|
9331
|
-
const
|
|
9332
|
-
outputResult({ sessions }, mode);
|
|
10146
|
+
const sessions2 = await forwardSessionList();
|
|
10147
|
+
outputResult({ sessions: sessions2 }, mode);
|
|
9333
10148
|
} catch {
|
|
9334
|
-
const
|
|
9335
|
-
outputResult({ sessions }, mode);
|
|
10149
|
+
const sessions2 = await listSessions();
|
|
10150
|
+
outputResult({ sessions: sessions2 }, mode);
|
|
9336
10151
|
}
|
|
9337
10152
|
break;
|
|
9338
10153
|
}
|
|
@@ -9352,8 +10167,8 @@ async function handleSession(args, options, mode, cdpEndpoint) {
|
|
|
9352
10167
|
}
|
|
9353
10168
|
case "kill-all": {
|
|
9354
10169
|
try {
|
|
9355
|
-
const
|
|
9356
|
-
for (const s of
|
|
10170
|
+
const sessions2 = await forwardSessionList();
|
|
10171
|
+
for (const s of sessions2) {
|
|
9357
10172
|
try {
|
|
9358
10173
|
await forwardSessionClose(s.name);
|
|
9359
10174
|
} catch {
|
|
@@ -9380,15 +10195,28 @@ function getPluginLoader2() {
|
|
|
9380
10195
|
if (!pluginLoader3) pluginLoader3 = new XBrowserPluginLoader();
|
|
9381
10196
|
return pluginLoader3;
|
|
9382
10197
|
}
|
|
9383
|
-
async function
|
|
10198
|
+
async function buildRuntimePluginInfo() {
|
|
9384
10199
|
const loader = await getPluginLoader();
|
|
9385
10200
|
const sites = loader.getCore().loader.getSites();
|
|
9386
10201
|
const map = /* @__PURE__ */ new Map();
|
|
9387
10202
|
for (const site of sites) {
|
|
9388
|
-
const cmds = site.getAllCommands()
|
|
9389
|
-
|
|
9390
|
-
|
|
10203
|
+
const cmds = site.getAllCommands();
|
|
10204
|
+
const commandNames = cmds.map((c) => c.name);
|
|
10205
|
+
if (commandNames.length === 0) continue;
|
|
10206
|
+
const anySite = site;
|
|
10207
|
+
const hasLoginHandler = typeof anySite.hasLoginCommand === "function" && anySite.hasLoginCommand();
|
|
10208
|
+
const configRequiresLogin = !!site.config.requiresLogin;
|
|
10209
|
+
const hasLogin = hasLoginHandler || configRequiresLogin;
|
|
10210
|
+
let loggedIn = null;
|
|
10211
|
+
if (hasLogin) {
|
|
10212
|
+
try {
|
|
10213
|
+
loggedIn = await site.isLoggedIn();
|
|
10214
|
+
} catch {
|
|
10215
|
+
loggedIn = null;
|
|
10216
|
+
}
|
|
9391
10217
|
}
|
|
10218
|
+
const requiresLoginCommands = cmds.filter((c) => c.requiresLogin === true).map((c) => c.name);
|
|
10219
|
+
map.set(site.name, { commands: commandNames, hasLogin, loggedIn, requiresLoginCommands });
|
|
9392
10220
|
}
|
|
9393
10221
|
return map;
|
|
9394
10222
|
}
|
|
@@ -9552,6 +10380,54 @@ async function handlePluginInfo(args, options, mode) {
|
|
|
9552
10380
|
console.error("\u67E5\u8BE2\u5931\u8D25:", err.message);
|
|
9553
10381
|
}
|
|
9554
10382
|
}
|
|
10383
|
+
async function handlePluginSchema(args, mode) {
|
|
10384
|
+
const pluginName = args[0];
|
|
10385
|
+
const commandName = args[1];
|
|
10386
|
+
if (!pluginName) outputError("Usage: xbrowser plugin schema <name> [command] [--json]");
|
|
10387
|
+
const loader = await getPluginLoader();
|
|
10388
|
+
const contract = loader.getPluginContract(pluginName, commandName);
|
|
10389
|
+
if (!contract) {
|
|
10390
|
+
outputError(commandName ? `Command "${commandName}" not found in plugin "${pluginName}"` : `Plugin "${pluginName}" not found`);
|
|
10391
|
+
return;
|
|
10392
|
+
}
|
|
10393
|
+
if (mode === "json") {
|
|
10394
|
+
outputResult(contract, mode);
|
|
10395
|
+
return;
|
|
10396
|
+
}
|
|
10397
|
+
if ("commands" in contract) {
|
|
10398
|
+
printPluginContract(contract);
|
|
10399
|
+
} else {
|
|
10400
|
+
printCommandContract(pluginName, contract);
|
|
10401
|
+
}
|
|
10402
|
+
}
|
|
10403
|
+
function printPluginContract(contract) {
|
|
10404
|
+
console.log(`${contract.plugin.name} contract v${contract.version}`);
|
|
10405
|
+
if (contract.plugin.description) console.log(contract.plugin.description);
|
|
10406
|
+
console.log("");
|
|
10407
|
+
for (const command of contract.commands) {
|
|
10408
|
+
printCommandContract(contract.plugin.name, command);
|
|
10409
|
+
}
|
|
10410
|
+
}
|
|
10411
|
+
function printCommandContract(pluginName, command) {
|
|
10412
|
+
console.log(`${pluginName} ${command.name}`);
|
|
10413
|
+
if (command.description) console.log(` ${command.description}`);
|
|
10414
|
+
console.log(` scope: ${command.scope}`);
|
|
10415
|
+
if (command.capabilities.length > 0) {
|
|
10416
|
+
console.log(` capabilities: ${command.capabilities.join(", ")}`);
|
|
10417
|
+
}
|
|
10418
|
+
if (command.positional.length > 0) {
|
|
10419
|
+
console.log(` positional: ${command.positional.join(", ")}`);
|
|
10420
|
+
}
|
|
10421
|
+
if (command.form.fields.length > 0) {
|
|
10422
|
+
console.log(" fields:");
|
|
10423
|
+
for (const field of command.form.fields) {
|
|
10424
|
+
const required = field.required ? "required" : "optional";
|
|
10425
|
+
const choices = field.enum ? ` [${field.enum.join("|")}]` : "";
|
|
10426
|
+
console.log(` --${field.name}: ${field.type}/${field.widget} ${required}${choices}`);
|
|
10427
|
+
}
|
|
10428
|
+
}
|
|
10429
|
+
console.log("");
|
|
10430
|
+
}
|
|
9555
10431
|
async function handlePlugin(args, options, mode) {
|
|
9556
10432
|
const sub = args[0];
|
|
9557
10433
|
const subArgs = args.slice(1);
|
|
@@ -9592,17 +10468,20 @@ async function handlePlugin(args, options, mode) {
|
|
|
9592
10468
|
}
|
|
9593
10469
|
case "list": {
|
|
9594
10470
|
const plugins = await installer.list();
|
|
9595
|
-
const
|
|
10471
|
+
const runtimeInfo = await buildRuntimePluginInfo();
|
|
9596
10472
|
const enrichedPlugins = plugins.map((p) => {
|
|
9597
10473
|
const metadata = p.metadata;
|
|
9598
10474
|
const staticCommands = metadata?.commands;
|
|
9599
|
-
const
|
|
9600
|
-
const commands =
|
|
10475
|
+
const rt = runtimeInfo.get(p.name);
|
|
10476
|
+
const commands = rt?.commands || staticCommands;
|
|
9601
10477
|
return {
|
|
9602
10478
|
...p,
|
|
9603
10479
|
commands,
|
|
9604
10480
|
version: metadata?.version,
|
|
9605
|
-
description: metadata?.description
|
|
10481
|
+
description: metadata?.description,
|
|
10482
|
+
hasLogin: rt?.hasLogin ?? false,
|
|
10483
|
+
loggedIn: rt?.loggedIn ?? null,
|
|
10484
|
+
requiresLoginCommands: rt?.requiresLoginCommands ?? []
|
|
9606
10485
|
};
|
|
9607
10486
|
});
|
|
9608
10487
|
if (mode === "json") {
|
|
@@ -9613,20 +10492,27 @@ async function handlePlugin(args, options, mode) {
|
|
|
9613
10492
|
return;
|
|
9614
10493
|
}
|
|
9615
10494
|
for (const p of enrichedPlugins) {
|
|
10495
|
+
const loginTag = p.hasLogin ? p.loggedIn ? " [logged in]" : " [need login]" : "";
|
|
9616
10496
|
if (p.version && p.description) {
|
|
9617
|
-
console.log(`${p.name} (${p.version}) - ${p.description}`);
|
|
10497
|
+
console.log(`${p.name} (${p.version}) - ${p.description}${loginTag}`);
|
|
9618
10498
|
} else {
|
|
9619
|
-
console.log(p.name);
|
|
10499
|
+
console.log(`${p.name}${loginTag}`);
|
|
9620
10500
|
}
|
|
9621
10501
|
if (p.commands && p.commands.length > 0) {
|
|
9622
10502
|
console.log(` ${p.commands.join(", ")}`);
|
|
9623
10503
|
}
|
|
10504
|
+
if (p.requiresLoginCommands.length > 0) {
|
|
10505
|
+
console.log(` requires login: ${p.requiresLoginCommands.join(", ")}`);
|
|
10506
|
+
}
|
|
9624
10507
|
}
|
|
9625
10508
|
console.log(`
|
|
9626
10509
|
Total: ${enrichedPlugins.length} plugins`);
|
|
9627
10510
|
}
|
|
9628
10511
|
break;
|
|
9629
10512
|
}
|
|
10513
|
+
case "schema":
|
|
10514
|
+
await handlePluginSchema(subArgs, mode);
|
|
10515
|
+
break;
|
|
9630
10516
|
case "reload": {
|
|
9631
10517
|
const name = subArgs[0];
|
|
9632
10518
|
if (!name) outputError("Usage: xbrowser plugin reload <name>");
|
|
@@ -10021,7 +10907,7 @@ async function handleFilter(args, _mode) {
|
|
|
10021
10907
|
|
|
10022
10908
|
// src/stdin.ts
|
|
10023
10909
|
import { createInterface } from "readline";
|
|
10024
|
-
import { readFileSync as
|
|
10910
|
+
import { readFileSync as readFileSync14 } from "fs";
|
|
10025
10911
|
async function readStdin2() {
|
|
10026
10912
|
if (process.stdin.isTTY) return [];
|
|
10027
10913
|
const lines = [];
|
|
@@ -10035,7 +10921,7 @@ async function readStdin2() {
|
|
|
10035
10921
|
return lines;
|
|
10036
10922
|
}
|
|
10037
10923
|
function readCommandFile(filePath) {
|
|
10038
|
-
const content =
|
|
10924
|
+
const content = readFileSync14(filePath, "utf-8");
|
|
10039
10925
|
return content.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#"));
|
|
10040
10926
|
}
|
|
10041
10927
|
|
|
@@ -10667,14 +11553,14 @@ async function listCommands() {
|
|
|
10667
11553
|
return jsonResponse(200, { commands });
|
|
10668
11554
|
}
|
|
10669
11555
|
async function listSessions2() {
|
|
10670
|
-
const
|
|
11556
|
+
const sessions2 = getAllSessions().map((s) => ({
|
|
10671
11557
|
id: s.id,
|
|
10672
11558
|
name: s.name,
|
|
10673
11559
|
url: s.page?.url() ?? null,
|
|
10674
11560
|
createdAt: s.createdAt,
|
|
10675
11561
|
isCDP: s.isCDP ?? false
|
|
10676
11562
|
}));
|
|
10677
|
-
return jsonResponse(200, { sessions });
|
|
11563
|
+
return jsonResponse(200, { sessions: sessions2 });
|
|
10678
11564
|
}
|
|
10679
11565
|
async function createSessionHandler(req) {
|
|
10680
11566
|
const body = req.body;
|
|
@@ -11072,8 +11958,23 @@ async function handleEvalMode(argv) {
|
|
|
11072
11958
|
}
|
|
11073
11959
|
async function handleChainInput(input, argv) {
|
|
11074
11960
|
const cdpEndpoint = argv ? extractCdpFromArgv(argv) : void 0;
|
|
11961
|
+
const jsonMode = argv ? argv.includes("--json") || argv.includes("-j") : false;
|
|
11075
11962
|
const chainResult = await executeChain(input, { cdpEndpoint });
|
|
11076
|
-
|
|
11963
|
+
if (jsonMode) {
|
|
11964
|
+
const output = {
|
|
11965
|
+
success: chainResult.success,
|
|
11966
|
+
steps: chainResult.steps.map((s) => ({
|
|
11967
|
+
command: s.raw,
|
|
11968
|
+
success: s.success,
|
|
11969
|
+
data: s.data,
|
|
11970
|
+
duration: s.duration,
|
|
11971
|
+
...s.hookOutputs?.length ? { hooks: s.hookOutputs } : {}
|
|
11972
|
+
}))
|
|
11973
|
+
};
|
|
11974
|
+
console.log(JSON.stringify(output, null, 2));
|
|
11975
|
+
} else {
|
|
11976
|
+
printChainResult(chainResult);
|
|
11977
|
+
}
|
|
11077
11978
|
if (!chainResult.success) throw new Error("Command failed");
|
|
11078
11979
|
}
|
|
11079
11980
|
async function routeCommand(argv, stdinCommands) {
|
|
@@ -11098,21 +11999,6 @@ async function routeCommand(argv, stdinCommands) {
|
|
|
11098
11999
|
const { positional, options } = parsed;
|
|
11099
12000
|
const command = positional[0];
|
|
11100
12001
|
const cmdArgs = positional.slice(1);
|
|
11101
|
-
const isPluginCommand = command ? !allBuiltins.find((b) => b.name === command) && !getCommand(command) && command !== "help" : false;
|
|
11102
|
-
if (!isPluginCommand && command) {
|
|
11103
|
-
const unknownOptions = Object.keys(options).filter((k) => !KNOWN_GLOBAL_OPTIONS.has(k));
|
|
11104
|
-
if (unknownOptions.length > 0) {
|
|
11105
|
-
const unknown = unknownOptions.map((k) => `--${k}`).join(", ");
|
|
11106
|
-
outputError(
|
|
11107
|
-
`Unknown option: ${unknown}
|
|
11108
|
-
Did you mean to use a global flag? Global flags must come BEFORE the command:
|
|
11109
|
-
\u2717 xbrowser goto <url> --cdp-endpoint <endpoint> (treated as unknown)
|
|
11110
|
-
\u2713 xbrowser --cdp <endpoint> goto <url> (correct)
|
|
11111
|
-
Run "xbrowser --help" to see available global options.`
|
|
11112
|
-
);
|
|
11113
|
-
return;
|
|
11114
|
-
}
|
|
11115
|
-
}
|
|
11116
12002
|
const mode = options.json ? "json" : options.yaml ? "yaml" : "text";
|
|
11117
12003
|
const sessionName = options.session || process.env.XBROWSER_SESSION || "default";
|
|
11118
12004
|
const cdpEndpoint = options.cdp;
|
|
@@ -11361,11 +12247,11 @@ Run "xbrowser ${command} ${subCommand} --help" to see available parameters.`
|
|
|
11361
12247
|
if (options.target && !params._target) {
|
|
11362
12248
|
params._target = options.target;
|
|
11363
12249
|
}
|
|
11364
|
-
const needsBrowser = cmdEntry.scope === "page";
|
|
12250
|
+
const needsBrowser = cmdEntry.scope === "page" || cmdEntry.scope === "browser";
|
|
11365
12251
|
if (needsBrowser && !process.env.XBROWSER_DAEMON_WORKER) {
|
|
11366
|
-
const { forwardExec } = await import("./daemon-client-
|
|
12252
|
+
const { forwardExec } = await import("./daemon-client-YAVQ343A.js");
|
|
11367
12253
|
const userTimeout = typeof params.timeout === "number" && params.timeout > 0 ? params.timeout * 1e3 + 3e4 : void 0;
|
|
11368
|
-
const result = await forwardExec(command
|
|
12254
|
+
const result = await forwardExec(`${command}.${subCommand}`, params, sessionName, cdpEndpoint, userTimeout);
|
|
11369
12255
|
if (result && result.success !== false) {
|
|
11370
12256
|
if (isCommandResult2(result)) {
|
|
11371
12257
|
if (mode === "json" || mode === "yaml") {
|
|
@@ -11398,6 +12284,7 @@ Run "xbrowser ${command} ${subCommand} --help" to see available parameters.`
|
|
|
11398
12284
|
browser: needsBrowser ? session.context.browser() : null,
|
|
11399
12285
|
browserContext: needsBrowser ? session.context : null,
|
|
11400
12286
|
sessionId: needsBrowser ? session.id : "",
|
|
12287
|
+
cdpEndpoint: cdpEndpoint || (needsBrowser ? session?.cdpEndpoint : void 0),
|
|
11401
12288
|
storage: getPluginStorage(command),
|
|
11402
12289
|
output: { mode, showTips: true, color: true, emoji: true },
|
|
11403
12290
|
error: (msg) => {
|
|
@@ -11411,38 +12298,46 @@ Run "xbrowser ${command} ${subCommand} --help" to see available parameters.`
|
|
|
11411
12298
|
}
|
|
11412
12299
|
};
|
|
11413
12300
|
try {
|
|
12301
|
+
const cmdStart = Date.now();
|
|
12302
|
+
const cmdHooks = await loadHooks();
|
|
12303
|
+
if (cmdHooks.length > 0 && session?.page) {
|
|
12304
|
+
await Promise.all(cmdHooks.map((h) => h.onBeforeCommand?.({ page: session.page, command: `${command} ${subCommand}`, params })));
|
|
12305
|
+
}
|
|
11414
12306
|
const result = await cmdEntry.handler(params, ctx);
|
|
12307
|
+
const hookOutputs = [];
|
|
12308
|
+
if (cmdHooks.length > 0 && session?.page) {
|
|
12309
|
+
for (const h of cmdHooks) {
|
|
12310
|
+
const output = await h.onAfterCommand?.({ page: session.page, command: `${command} ${subCommand}`, params, result, duration: Date.now() - cmdStart });
|
|
12311
|
+
if (output) hookOutputs.push({ _hook: h.name, ...output });
|
|
12312
|
+
}
|
|
12313
|
+
}
|
|
11415
12314
|
if (session && result && result.data) {
|
|
11416
12315
|
const convUrl = result.data.conversationUrl;
|
|
11417
12316
|
if (convUrl) {
|
|
11418
12317
|
saveSessionDiskMeta(sessionName, { conversationUrl: convUrl, cdpEndpoint });
|
|
11419
12318
|
}
|
|
11420
12319
|
}
|
|
11421
|
-
|
|
11422
|
-
|
|
11423
|
-
|
|
11424
|
-
|
|
11425
|
-
|
|
11426
|
-
|
|
11427
|
-
|
|
11428
|
-
|
|
11429
|
-
if (result.tips?.length) {
|
|
11430
|
-
for (const tip of result.tips) console.log(` \u{1F4A1} ${tip}`);
|
|
11431
|
-
}
|
|
12320
|
+
const outputData = isCommandResult2(result) ? result.data : result && typeof result === "object" ? result.data ?? result : result;
|
|
12321
|
+
const tips = isCommandResult2(result) ? result.tips : result && typeof result === "object" ? result.tips : void 0;
|
|
12322
|
+
if (mode === "json" || mode === "yaml") {
|
|
12323
|
+
const finalOutput = {
|
|
12324
|
+
data: outputData
|
|
12325
|
+
};
|
|
12326
|
+
if (hookOutputs.length > 0) {
|
|
12327
|
+
finalOutput.hooks = hookOutputs;
|
|
11432
12328
|
}
|
|
11433
|
-
|
|
11434
|
-
|
|
11435
|
-
|
|
11436
|
-
|
|
11437
|
-
|
|
11438
|
-
|
|
11439
|
-
|
|
11440
|
-
}
|
|
11441
|
-
}
|
|
11442
|
-
|
|
11443
|
-
const
|
|
11444
|
-
|
|
11445
|
-
for (const tip of tips) console.log(` \u{1F4A1} ${tip}`);
|
|
12329
|
+
console.log(outputFormatter2.format(finalOutput, { mode, color: false, emoji: false }));
|
|
12330
|
+
if (tips?.length) {
|
|
12331
|
+
for (const tip of tips) console.error(`\u{1F4A1} ${tip}`);
|
|
12332
|
+
}
|
|
12333
|
+
} else {
|
|
12334
|
+
console.log(outputFormatter2.format(outputData, { mode: "text", color: true, emoji: true }));
|
|
12335
|
+
if (tips?.length) {
|
|
12336
|
+
for (const tip of tips) console.log(` \u{1F4A1} ${tip}`);
|
|
12337
|
+
}
|
|
12338
|
+
if (hookOutputs.length > 0) {
|
|
12339
|
+
for (const ho of hookOutputs) {
|
|
12340
|
+
console.log(` \u{1F4F8} screenshot: ${ho.screenshot?.url || "captured"}`);
|
|
11446
12341
|
}
|
|
11447
12342
|
}
|
|
11448
12343
|
}
|
|
@@ -12388,7 +13283,7 @@ var WSServer = class extends EventEmitter {
|
|
|
12388
13283
|
const vp = this.lastFrameViewport || initSc.page.viewportSize() || await initSc.page.evaluate(() => ({ width: window.innerWidth, height: window.innerHeight }));
|
|
12389
13284
|
this.sendToClient(clientId, {
|
|
12390
13285
|
type: "status",
|
|
12391
|
-
data: { status: "connected", sessionId: client.sessionId || void 0, viewport: vp }
|
|
13286
|
+
data: { status: "connected", sessionId: client.sessionId || void 0, viewport: vp ?? void 0 }
|
|
12392
13287
|
});
|
|
12393
13288
|
} catch {
|
|
12394
13289
|
}
|
|
@@ -12653,10 +13548,10 @@ var WSServer = class extends EventEmitter {
|
|
|
12653
13548
|
}
|
|
12654
13549
|
case "file_download": {
|
|
12655
13550
|
try {
|
|
12656
|
-
const { readFileSync:
|
|
13551
|
+
const { readFileSync: readFileSync16 } = await import("fs");
|
|
12657
13552
|
const { resolve: resolve16, basename: basename3 } = await import("path");
|
|
12658
13553
|
const targetPath = resolve16(msg.path);
|
|
12659
|
-
const data =
|
|
13554
|
+
const data = readFileSync16(targetPath);
|
|
12660
13555
|
const base64 = data.toString("base64");
|
|
12661
13556
|
const ext = targetPath.split(".").pop()?.toLowerCase() || "";
|
|
12662
13557
|
const mimeMap = {
|
|
@@ -12806,7 +13701,7 @@ var WSServer = class extends EventEmitter {
|
|
|
12806
13701
|
const vp = this.lastFrameViewport || sc.page.viewportSize() || await sc.page.evaluate(() => ({ width: window.innerWidth, height: window.innerHeight }));
|
|
12807
13702
|
this.sendToClient(clientId, {
|
|
12808
13703
|
type: "status",
|
|
12809
|
-
data: { status: "connected", sessionId, viewport: vp }
|
|
13704
|
+
data: { status: "connected", sessionId, viewport: vp ?? void 0 }
|
|
12810
13705
|
});
|
|
12811
13706
|
} catch {
|
|
12812
13707
|
}
|
|
@@ -13937,8 +14832,8 @@ var DataCollector = class {
|
|
|
13937
14832
|
return results;
|
|
13938
14833
|
}
|
|
13939
14834
|
async createBrowserContext() {
|
|
13940
|
-
const {
|
|
13941
|
-
const browser = await
|
|
14835
|
+
const { launch } = await import("./cdp-driver-SSXUGXP6.js");
|
|
14836
|
+
const { browser } = await launch({
|
|
13942
14837
|
headless: true,
|
|
13943
14838
|
args: ["--no-sandbox", "--disable-setuid-sandbox"]
|
|
13944
14839
|
});
|
|
@@ -13999,6 +14894,7 @@ export {
|
|
|
13999
14894
|
WSServer,
|
|
14000
14895
|
WebhookNotifier,
|
|
14001
14896
|
XBrowserPluginLoader,
|
|
14897
|
+
actOnPage,
|
|
14002
14898
|
advise,
|
|
14003
14899
|
allBuiltins,
|
|
14004
14900
|
assertPageScope,
|
|
@@ -14038,6 +14934,7 @@ export {
|
|
|
14038
14934
|
getCommandNames,
|
|
14039
14935
|
getCompanyType,
|
|
14040
14936
|
getDaemonProcessStatus,
|
|
14937
|
+
getPageScreenHash,
|
|
14041
14938
|
getPlatformName,
|
|
14042
14939
|
getSessionPage,
|
|
14043
14940
|
getWSServerFromCache,
|
|
@@ -14048,6 +14945,7 @@ export {
|
|
|
14048
14945
|
mouseTrajectoryRule,
|
|
14049
14946
|
networkAnomalyRule,
|
|
14050
14947
|
normalizeSelector,
|
|
14948
|
+
observePage,
|
|
14051
14949
|
openSession,
|
|
14052
14950
|
pageLifecycleRule,
|
|
14053
14951
|
parseCommandArgs,
|
|
@@ -14064,5 +14962,6 @@ export {
|
|
|
14064
14962
|
splitCommand,
|
|
14065
14963
|
startDaemonProcess,
|
|
14066
14964
|
stopDaemonProcess,
|
|
14067
|
-
version
|
|
14965
|
+
version,
|
|
14966
|
+
waitForPage
|
|
14068
14967
|
};
|