@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/daemon-main.js
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
} from "./chunk-VEDJ5XSQ.js";
|
|
6
6
|
import {
|
|
7
7
|
SessionRecorder
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-2MFXKN32.js";
|
|
9
9
|
import {
|
|
10
10
|
closeEphemeralContext,
|
|
11
11
|
closeSessionByName,
|
|
@@ -21,16 +21,18 @@ import {
|
|
|
21
21
|
resolveLaunchOpts,
|
|
22
22
|
saveSessionDiskMeta,
|
|
23
23
|
setActivePage
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-E4O5ZU3H.js";
|
|
25
|
+
import "./chunk-T4J4C2NZ.js";
|
|
26
|
+
import "./chunk-3RG5ZIWI.js";
|
|
25
27
|
|
|
26
28
|
// src/daemon/daemon-main.ts
|
|
27
|
-
import { writeFileSync as
|
|
29
|
+
import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync5, appendFileSync } from "fs";
|
|
28
30
|
import { join as join6 } from "path";
|
|
29
31
|
import { homedir as homedir6 } from "os";
|
|
30
32
|
import { startHttpServer } from "@dyyz1993/xcli-core";
|
|
31
33
|
|
|
32
34
|
// src/daemon/rpc-handlers.ts
|
|
33
|
-
import { writeFileSync as
|
|
35
|
+
import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync4 } from "fs";
|
|
34
36
|
import { join as join5 } from "path";
|
|
35
37
|
import { homedir as homedir5 } from "os";
|
|
36
38
|
import {
|
|
@@ -40,12 +42,13 @@ import {
|
|
|
40
42
|
|
|
41
43
|
// src/executor.ts
|
|
42
44
|
import {
|
|
43
|
-
ok as
|
|
45
|
+
ok as ok26,
|
|
44
46
|
fail as fail7,
|
|
45
47
|
isCommandResult,
|
|
46
48
|
configureArchiveStore,
|
|
47
49
|
appendCommandToArchive,
|
|
48
|
-
checkGuard
|
|
50
|
+
checkGuard,
|
|
51
|
+
PluginStorage
|
|
49
52
|
} from "@dyyz1993/xcli-core";
|
|
50
53
|
|
|
51
54
|
// src/utils/positional-params.ts
|
|
@@ -515,7 +518,7 @@ var pressCommand = registerCommand({
|
|
|
515
518
|
key: z2.string()
|
|
516
519
|
}),
|
|
517
520
|
handler: async (p, ctx) => {
|
|
518
|
-
await ctx.page.press(p.selector || "body", p.key, {
|
|
521
|
+
await ctx.page.press(p.selector || "body", p.key, { timeout: 1e4 });
|
|
519
522
|
return ok2({ key: p.key });
|
|
520
523
|
}
|
|
521
524
|
});
|
|
@@ -534,7 +537,7 @@ var selectCommand = registerCommand({
|
|
|
534
537
|
}),
|
|
535
538
|
handler: async (p, ctx) => {
|
|
536
539
|
const values = typeof p.value === "string" ? [p.value] : p.value;
|
|
537
|
-
await ctx.page.selectOption(p.selector, values
|
|
540
|
+
await ctx.page.selectOption(p.selector, values);
|
|
538
541
|
return ok2({ selector: p.selector, value: p.value });
|
|
539
542
|
}
|
|
540
543
|
});
|
|
@@ -550,7 +553,7 @@ var checkCommand = registerCommand({
|
|
|
550
553
|
selector: z2.string()
|
|
551
554
|
}),
|
|
552
555
|
handler: async (p, ctx) => {
|
|
553
|
-
await ctx.page.check(p.selector, {
|
|
556
|
+
await ctx.page.check(p.selector, { timeout: 1e4 });
|
|
554
557
|
return ok2({ selector: p.selector });
|
|
555
558
|
}
|
|
556
559
|
});
|
|
@@ -567,7 +570,7 @@ var hoverCommand = registerCommand({
|
|
|
567
570
|
selector: z2.string()
|
|
568
571
|
}),
|
|
569
572
|
handler: async (p, ctx) => {
|
|
570
|
-
await ctx.page.hover(p.selector, {
|
|
573
|
+
await ctx.page.hover(p.selector, { timeout: 1e4 });
|
|
571
574
|
return ok2({ selector: p.selector });
|
|
572
575
|
}
|
|
573
576
|
});
|
|
@@ -2096,7 +2099,7 @@ var scrapeCommand = registerCommand({
|
|
|
2096
2099
|
await page.goto(p.url, { waitUntil: "commit", timeout: p.timeout });
|
|
2097
2100
|
await page.waitForSelector("body", { timeout: p.timeout }).catch(() => {
|
|
2098
2101
|
});
|
|
2099
|
-
await page.waitForLoadState("networkidle",
|
|
2102
|
+
await page.waitForLoadState("networkidle", Math.min(p.timeout, 8e3)).catch(() => {
|
|
2100
2103
|
});
|
|
2101
2104
|
await page.waitForTimeout(p.waitAfterLoad > 0 ? p.waitAfterLoad : 2e3);
|
|
2102
2105
|
if (p.selector) {
|
|
@@ -3101,7 +3104,7 @@ async function fetchFullContent(urls, timeout, cdpEndpoint) {
|
|
|
3101
3104
|
const pg = await context.newPage();
|
|
3102
3105
|
try {
|
|
3103
3106
|
await pg.goto(url, { waitUntil: "domcontentloaded", timeout });
|
|
3104
|
-
await pg.waitForLoadState("networkidle",
|
|
3107
|
+
await pg.waitForLoadState("networkidle", timeout).catch(() => {
|
|
3105
3108
|
});
|
|
3106
3109
|
const html = await pg.content();
|
|
3107
3110
|
contentMap.set(url, htmlToMarkdown(html, { onlyMainContent: true }));
|
|
@@ -3497,7 +3500,7 @@ var networkCommand = registerCommand({
|
|
|
3497
3500
|
waitUntil: "domcontentloaded",
|
|
3498
3501
|
timeout: p.timeout
|
|
3499
3502
|
});
|
|
3500
|
-
await page.waitForLoadState("networkidle",
|
|
3503
|
+
await page.waitForLoadState("networkidle", p.timeout).catch(() => {
|
|
3501
3504
|
});
|
|
3502
3505
|
await page.waitForTimeout(p.wait);
|
|
3503
3506
|
const totalCount = captures.length;
|
|
@@ -3808,6 +3811,24 @@ var ENGINE_KEY_ENUM = z20.enum(ALL_ENGINE_KEYS);
|
|
|
3808
3811
|
import { z as z21 } from "zod";
|
|
3809
3812
|
import { ok as ok20, fail as fail4 } from "@dyyz1993/xcli-core";
|
|
3810
3813
|
|
|
3814
|
+
// src/runtime/ref-store.ts
|
|
3815
|
+
var sessions = /* @__PURE__ */ new Map();
|
|
3816
|
+
function normalizeAgentRef(ref) {
|
|
3817
|
+
return ref.startsWith("@") ? ref.slice(1) : ref;
|
|
3818
|
+
}
|
|
3819
|
+
function replaceRefs(sessionKey2, screenHash, targets) {
|
|
3820
|
+
sessions.set(sessionKey2, {
|
|
3821
|
+
screenHash,
|
|
3822
|
+
targets: new Map(targets.map((target) => [target.ref, target]))
|
|
3823
|
+
});
|
|
3824
|
+
}
|
|
3825
|
+
function getRefTarget(sessionKey2, ref) {
|
|
3826
|
+
const session = sessions.get(sessionKey2);
|
|
3827
|
+
const target = session?.targets.get(normalizeAgentRef(ref));
|
|
3828
|
+
if (!session || !target) return null;
|
|
3829
|
+
return { screenHash: session.screenHash, target };
|
|
3830
|
+
}
|
|
3831
|
+
|
|
3811
3832
|
// src/utils/resolve-selector.ts
|
|
3812
3833
|
function buildElementSelector(el) {
|
|
3813
3834
|
function isUnique(sel) {
|
|
@@ -3979,9 +4000,9 @@ async function resolveSelectors(page, ariaSnapshot) {
|
|
|
3979
4000
|
}
|
|
3980
4001
|
return results;
|
|
3981
4002
|
}
|
|
3982
|
-
var REF_ONLY =
|
|
4003
|
+
var REF_ONLY = /^@?(e\d+)$/;
|
|
3983
4004
|
var refCache = /* @__PURE__ */ new Map();
|
|
3984
|
-
async function resolveRefParams(page, params, selectorKeys, cache) {
|
|
4005
|
+
async function resolveRefParams(page, params, selectorKeys, cache, sessionId) {
|
|
3985
4006
|
const tips = [];
|
|
3986
4007
|
const newParams = { ...params };
|
|
3987
4008
|
if (!selectorKeys || selectorKeys.length === 0) {
|
|
@@ -3990,7 +4011,17 @@ async function resolveRefParams(page, params, selectorKeys, cache) {
|
|
|
3990
4011
|
for (const key of selectorKeys) {
|
|
3991
4012
|
const val = params[key];
|
|
3992
4013
|
if (typeof val !== "string" || !REF_ONLY.test(val)) continue;
|
|
3993
|
-
const
|
|
4014
|
+
const match = val.match(REF_ONLY);
|
|
4015
|
+
if (!match) continue;
|
|
4016
|
+
const ref = normalizeAgentRef(match[1]);
|
|
4017
|
+
if (sessionId) {
|
|
4018
|
+
const runtimeTarget = getRefTarget(sessionId, ref);
|
|
4019
|
+
if (runtimeTarget) {
|
|
4020
|
+
tips.push(`ref=@${ref} (${key}) => ${runtimeTarget.target.selector} (observe)`);
|
|
4021
|
+
newParams[key] = runtimeTarget.target.selector;
|
|
4022
|
+
continue;
|
|
4023
|
+
}
|
|
4024
|
+
}
|
|
3994
4025
|
const activeCache = cache ?? refCache;
|
|
3995
4026
|
const cached = activeCache.get(ref);
|
|
3996
4027
|
if (cached) {
|
|
@@ -4271,6 +4302,404 @@ async function enhanceSemanticsWithLLM(url, ariaSnapshot, ruleBasedElements) {
|
|
|
4271
4302
|
saveSemantics(domain, pathKey, url, llmElements);
|
|
4272
4303
|
}
|
|
4273
4304
|
|
|
4305
|
+
// src/runtime/agent-runtime.ts
|
|
4306
|
+
function sessionKey(sessionId) {
|
|
4307
|
+
return sessionId || "default";
|
|
4308
|
+
}
|
|
4309
|
+
async function observePage(page, sessionId, options = {}) {
|
|
4310
|
+
const [title, raw] = await Promise.all([
|
|
4311
|
+
page.title().catch(() => ""),
|
|
4312
|
+
page.evaluate(
|
|
4313
|
+
({ includeHidden, limit }) => {
|
|
4314
|
+
function hash(input) {
|
|
4315
|
+
let h = 2166136261;
|
|
4316
|
+
for (let i = 0; i < input.length; i++) {
|
|
4317
|
+
h ^= input.charCodeAt(i);
|
|
4318
|
+
h = Math.imul(h, 16777619);
|
|
4319
|
+
}
|
|
4320
|
+
return (h >>> 0).toString(16);
|
|
4321
|
+
}
|
|
4322
|
+
function cssEscape(value) {
|
|
4323
|
+
const css = globalThis.CSS;
|
|
4324
|
+
return css?.escape ? css.escape(value) : value.replace(/["\\#.:,[\]>+~*]/g, "\\$&");
|
|
4325
|
+
}
|
|
4326
|
+
function isUnique(selector2) {
|
|
4327
|
+
try {
|
|
4328
|
+
return document.querySelectorAll(selector2).length === 1;
|
|
4329
|
+
} catch {
|
|
4330
|
+
return false;
|
|
4331
|
+
}
|
|
4332
|
+
}
|
|
4333
|
+
function nthOfType(el) {
|
|
4334
|
+
const tag = el.tagName.toLowerCase();
|
|
4335
|
+
const parent = el.parentElement;
|
|
4336
|
+
if (!parent) return tag;
|
|
4337
|
+
const same = Array.from(parent.children).filter((child) => child.tagName === el.tagName);
|
|
4338
|
+
if (same.length === 1) return tag;
|
|
4339
|
+
return `${tag}:nth-of-type(${same.indexOf(el) + 1})`;
|
|
4340
|
+
}
|
|
4341
|
+
function selectorFor(el) {
|
|
4342
|
+
const tag = el.tagName.toLowerCase();
|
|
4343
|
+
const id = el.getAttribute("id");
|
|
4344
|
+
if (id) {
|
|
4345
|
+
const selector2 = `#${cssEscape(id)}`;
|
|
4346
|
+
if (isUnique(selector2)) return selector2;
|
|
4347
|
+
}
|
|
4348
|
+
for (const attr of ["data-testid", "data-test", "data-qa", "name", "aria-label"]) {
|
|
4349
|
+
const value = el.getAttribute(attr);
|
|
4350
|
+
if (!value) continue;
|
|
4351
|
+
const selector2 = `[${attr}="${cssEscape(value)}"]`;
|
|
4352
|
+
if (isUnique(selector2)) return selector2;
|
|
4353
|
+
const tagged = `${tag}${selector2}`;
|
|
4354
|
+
if (isUnique(tagged)) return tagged;
|
|
4355
|
+
}
|
|
4356
|
+
const classes = Array.from(el.classList).slice(0, 3);
|
|
4357
|
+
for (const cls of classes) {
|
|
4358
|
+
const selector2 = `${tag}.${cssEscape(cls)}`;
|
|
4359
|
+
if (isUnique(selector2)) return selector2;
|
|
4360
|
+
}
|
|
4361
|
+
const parts = [];
|
|
4362
|
+
let cur = el;
|
|
4363
|
+
while (cur && cur !== document.body && cur !== document.documentElement && parts.length < 6) {
|
|
4364
|
+
parts.unshift(nthOfType(cur));
|
|
4365
|
+
const selector2 = parts.join(" > ");
|
|
4366
|
+
if (isUnique(selector2)) return selector2;
|
|
4367
|
+
cur = cur.parentElement;
|
|
4368
|
+
}
|
|
4369
|
+
return parts.join(" > ") || tag;
|
|
4370
|
+
}
|
|
4371
|
+
function roleFor(el) {
|
|
4372
|
+
const explicit = el.getAttribute("role");
|
|
4373
|
+
if (explicit) return explicit;
|
|
4374
|
+
const tag = el.tagName.toLowerCase();
|
|
4375
|
+
if (tag === "a") return "link";
|
|
4376
|
+
if (tag === "button") return "button";
|
|
4377
|
+
if (tag === "select") return "combobox";
|
|
4378
|
+
if (tag === "textarea") return "textbox";
|
|
4379
|
+
if (tag === "input") {
|
|
4380
|
+
const type = (el.getAttribute("type") || "text").toLowerCase();
|
|
4381
|
+
if (type === "checkbox") return "checkbox";
|
|
4382
|
+
if (type === "radio") return "radio";
|
|
4383
|
+
if (type === "button" || type === "submit" || type === "reset") return "button";
|
|
4384
|
+
return "textbox";
|
|
4385
|
+
}
|
|
4386
|
+
return tag;
|
|
4387
|
+
}
|
|
4388
|
+
function textName(el) {
|
|
4389
|
+
const input = el;
|
|
4390
|
+
const direct = el.getAttribute("aria-label") || el.getAttribute("placeholder") || el.getAttribute("title") || input.value || el.textContent || "";
|
|
4391
|
+
return direct.replace(/\s+/g, " ").trim().slice(0, 120);
|
|
4392
|
+
}
|
|
4393
|
+
function actionsFor(role, editable) {
|
|
4394
|
+
const actions = [];
|
|
4395
|
+
if (editable) actions.push("fill", "type");
|
|
4396
|
+
if (role === "combobox") actions.push("select");
|
|
4397
|
+
if (role === "checkbox" || role === "radio") actions.push("check");
|
|
4398
|
+
actions.push("click", "hover");
|
|
4399
|
+
return Array.from(new Set(actions));
|
|
4400
|
+
}
|
|
4401
|
+
const selector = [
|
|
4402
|
+
"a[href]",
|
|
4403
|
+
"button",
|
|
4404
|
+
"input",
|
|
4405
|
+
"textarea",
|
|
4406
|
+
"select",
|
|
4407
|
+
"summary",
|
|
4408
|
+
"label",
|
|
4409
|
+
"[role]",
|
|
4410
|
+
"[tabindex]",
|
|
4411
|
+
'[contenteditable="true"]'
|
|
4412
|
+
].join(",");
|
|
4413
|
+
const candidates = Array.from(document.querySelectorAll(selector));
|
|
4414
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4415
|
+
const targets2 = [];
|
|
4416
|
+
for (const el of candidates) {
|
|
4417
|
+
const rect = el.getBoundingClientRect();
|
|
4418
|
+
const style = window.getComputedStyle(el);
|
|
4419
|
+
const visible = rect.width > 0 && rect.height > 0 && style.visibility !== "hidden" && style.display !== "none";
|
|
4420
|
+
if (!includeHidden && !visible) continue;
|
|
4421
|
+
const selectorValue = selectorFor(el);
|
|
4422
|
+
if (!selectorValue || seen.has(selectorValue)) continue;
|
|
4423
|
+
seen.add(selectorValue);
|
|
4424
|
+
const input = el;
|
|
4425
|
+
const tag = el.tagName.toLowerCase();
|
|
4426
|
+
const role = roleFor(el);
|
|
4427
|
+
const editable = tag === "textarea" || tag === "select" || tag === "input" && !["button", "submit", "reset", "checkbox", "radio"].includes((input.type || "").toLowerCase()) || el.isContentEditable;
|
|
4428
|
+
const enabled = !input.disabled && el.getAttribute("aria-disabled") !== "true";
|
|
4429
|
+
const checked = typeof input.checked === "boolean" && ["checkbox", "radio"].includes((input.type || "").toLowerCase()) ? input.checked : void 0;
|
|
4430
|
+
targets2.push({
|
|
4431
|
+
selector: selectorValue,
|
|
4432
|
+
role,
|
|
4433
|
+
name: textName(el),
|
|
4434
|
+
tag,
|
|
4435
|
+
visible,
|
|
4436
|
+
enabled,
|
|
4437
|
+
editable,
|
|
4438
|
+
...checked !== void 0 ? { checked } : {},
|
|
4439
|
+
...editable && input.value ? { value: input.value.slice(0, 120) } : {},
|
|
4440
|
+
...visible ? { box: { x: Math.round(rect.x), y: Math.round(rect.y), width: Math.round(rect.width), height: Math.round(rect.height) } } : {},
|
|
4441
|
+
actions: actionsFor(role, editable)
|
|
4442
|
+
});
|
|
4443
|
+
if (targets2.length >= limit) break;
|
|
4444
|
+
}
|
|
4445
|
+
const stateText = [
|
|
4446
|
+
location.href,
|
|
4447
|
+
document.title,
|
|
4448
|
+
document.body?.innerText?.slice(0, 5e3) || ""
|
|
4449
|
+
].join("\n");
|
|
4450
|
+
return { screenHash: hash(stateText), targets: targets2 };
|
|
4451
|
+
},
|
|
4452
|
+
{ includeHidden: !!options.includeHidden, limit: options.limit ?? 80 }
|
|
4453
|
+
)
|
|
4454
|
+
]);
|
|
4455
|
+
const targets = raw.targets.map((target, index) => ({
|
|
4456
|
+
ref: `e${index + 1}`,
|
|
4457
|
+
...target
|
|
4458
|
+
}));
|
|
4459
|
+
replaceRefs(sessionKey(sessionId), raw.screenHash, targets);
|
|
4460
|
+
return {
|
|
4461
|
+
url: page.url(),
|
|
4462
|
+
title,
|
|
4463
|
+
screenHash: raw.screenHash,
|
|
4464
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4465
|
+
targets
|
|
4466
|
+
};
|
|
4467
|
+
}
|
|
4468
|
+
function quoteName(name) {
|
|
4469
|
+
const cleaned = name.replace(/\s+/g, " ").trim();
|
|
4470
|
+
if (!cleaned) return "";
|
|
4471
|
+
return ` "${cleaned.replace(/"/g, '\\"')}"`;
|
|
4472
|
+
}
|
|
4473
|
+
function targetFlags(target) {
|
|
4474
|
+
const flags = [target.role || target.tag];
|
|
4475
|
+
if (!target.enabled) flags.push("disabled");
|
|
4476
|
+
if (target.editable) flags.push("editable");
|
|
4477
|
+
if (target.checked !== void 0) flags.push(target.checked ? "checked" : "unchecked");
|
|
4478
|
+
return flags.join(" ");
|
|
4479
|
+
}
|
|
4480
|
+
function buildSelectorMap(observation) {
|
|
4481
|
+
return Object.fromEntries(observation.targets.map((target) => [target.ref, target.selector]));
|
|
4482
|
+
}
|
|
4483
|
+
function formatObservationCompact(observation, options = {}) {
|
|
4484
|
+
const lines = [
|
|
4485
|
+
`Page: ${observation.title || "(untitled)"}`,
|
|
4486
|
+
`URL: ${observation.url}`,
|
|
4487
|
+
`Screen: ${observation.screenHash}`,
|
|
4488
|
+
""
|
|
4489
|
+
];
|
|
4490
|
+
if (observation.targets.length === 0) {
|
|
4491
|
+
lines.push("(no interactive targets)");
|
|
4492
|
+
} else {
|
|
4493
|
+
for (const target of observation.targets) {
|
|
4494
|
+
lines.push(`@${target.ref} [${targetFlags(target)}]${quoteName(target.name)}`);
|
|
4495
|
+
}
|
|
4496
|
+
}
|
|
4497
|
+
if (options.selectors && observation.targets.length > 0) {
|
|
4498
|
+
lines.push("", "## Selectors");
|
|
4499
|
+
lines.push(observation.targets.map((target) => `${target.ref}: ${target.selector}`).join(" | "));
|
|
4500
|
+
}
|
|
4501
|
+
return lines.join("\n");
|
|
4502
|
+
}
|
|
4503
|
+
async function getPageScreenHash(page) {
|
|
4504
|
+
const observation = await page.evaluate(() => {
|
|
4505
|
+
let h = 2166136261;
|
|
4506
|
+
const input = [location.href, document.title, document.body?.innerText?.slice(0, 5e3) || ""].join("\n");
|
|
4507
|
+
for (let i = 0; i < input.length; i++) {
|
|
4508
|
+
h ^= input.charCodeAt(i);
|
|
4509
|
+
h = Math.imul(h, 16777619);
|
|
4510
|
+
}
|
|
4511
|
+
return (h >>> 0).toString(16);
|
|
4512
|
+
});
|
|
4513
|
+
return observation;
|
|
4514
|
+
}
|
|
4515
|
+
async function actionability(page, selector) {
|
|
4516
|
+
return await page.evaluate((sel) => {
|
|
4517
|
+
const el = document.querySelector(sel);
|
|
4518
|
+
if (!el) return { ok: false, reason: "not_found" };
|
|
4519
|
+
const rect = el.getBoundingClientRect();
|
|
4520
|
+
const style = window.getComputedStyle(el);
|
|
4521
|
+
const visible = rect.width > 0 && rect.height > 0 && style.visibility !== "hidden" && style.display !== "none";
|
|
4522
|
+
if (!visible) return { ok: false, reason: "not_visible" };
|
|
4523
|
+
const input = el;
|
|
4524
|
+
const enabled = !input.disabled && el.getAttribute("aria-disabled") !== "true";
|
|
4525
|
+
if (!enabled) return { ok: false, reason: "disabled" };
|
|
4526
|
+
const cx = rect.left + rect.width / 2;
|
|
4527
|
+
const cy = rect.top + rect.height / 2;
|
|
4528
|
+
const hit = document.elementFromPoint(cx, cy);
|
|
4529
|
+
if (hit && hit !== el && !el.contains(hit) && !hit.contains(el)) {
|
|
4530
|
+
return { ok: false, reason: "covered" };
|
|
4531
|
+
}
|
|
4532
|
+
return { ok: true };
|
|
4533
|
+
}, selector);
|
|
4534
|
+
}
|
|
4535
|
+
async function actOnPage(page, sessionId, input) {
|
|
4536
|
+
const normalizedRef = input.ref ? normalizeAgentRef(input.ref) : void 0;
|
|
4537
|
+
const refMatch = normalizedRef ? getRefTarget(sessionKey(sessionId), normalizedRef) : null;
|
|
4538
|
+
const selector = input.selector || refMatch?.target.selector;
|
|
4539
|
+
if (!selector) {
|
|
4540
|
+
return {
|
|
4541
|
+
action: input.action,
|
|
4542
|
+
selector: "",
|
|
4543
|
+
ref: normalizedRef,
|
|
4544
|
+
success: false,
|
|
4545
|
+
reason: input.ref ? "unknown_ref" : "missing_target",
|
|
4546
|
+
message: normalizedRef ? `Ref "${input.ref}" not found. Run observe again.` : "Provide ref or selector."
|
|
4547
|
+
};
|
|
4548
|
+
}
|
|
4549
|
+
const hash = await getPageScreenHash(page).catch(() => void 0);
|
|
4550
|
+
const stale = !!(refMatch && hash && hash !== refMatch.screenHash);
|
|
4551
|
+
if (!input.force) {
|
|
4552
|
+
const check = await actionability(page, selector);
|
|
4553
|
+
if (!check.ok) {
|
|
4554
|
+
return {
|
|
4555
|
+
action: input.action,
|
|
4556
|
+
selector,
|
|
4557
|
+
ref: normalizedRef,
|
|
4558
|
+
success: false,
|
|
4559
|
+
reason: stale ? "stale_ref" : check.reason,
|
|
4560
|
+
message: stale ? `Ref "${input.ref}" may be stale. Run observe again.` : `Target is not actionable: ${check.reason}`,
|
|
4561
|
+
stale,
|
|
4562
|
+
screenHash: hash,
|
|
4563
|
+
target: refMatch?.target
|
|
4564
|
+
};
|
|
4565
|
+
}
|
|
4566
|
+
}
|
|
4567
|
+
const timeout = input.timeout ?? 1e4;
|
|
4568
|
+
try {
|
|
4569
|
+
switch (input.action) {
|
|
4570
|
+
case "click":
|
|
4571
|
+
await page.locator(selector).first().click({ timeout, force: !!input.force });
|
|
4572
|
+
break;
|
|
4573
|
+
case "fill":
|
|
4574
|
+
if (input.value === void 0) throw new Error("fill requires value");
|
|
4575
|
+
await page.locator(selector).first().fill(input.value, { timeout, force: !!input.force });
|
|
4576
|
+
break;
|
|
4577
|
+
case "type":
|
|
4578
|
+
if (input.value === void 0) throw new Error("type requires value");
|
|
4579
|
+
await page.locator(selector).first().pressSequentially(input.value, { timeout });
|
|
4580
|
+
break;
|
|
4581
|
+
case "press":
|
|
4582
|
+
if (!input.key) throw new Error("press requires key");
|
|
4583
|
+
await page.locator(selector).first().press(input.key, { timeout });
|
|
4584
|
+
break;
|
|
4585
|
+
case "select":
|
|
4586
|
+
if (input.value === void 0) throw new Error("select requires value");
|
|
4587
|
+
await page.locator(selector).first().selectOption(input.value);
|
|
4588
|
+
break;
|
|
4589
|
+
case "check":
|
|
4590
|
+
await page.locator(selector).first().check({ timeout });
|
|
4591
|
+
break;
|
|
4592
|
+
case "hover":
|
|
4593
|
+
await page.locator(selector).first().hover({ timeout });
|
|
4594
|
+
break;
|
|
4595
|
+
default: {
|
|
4596
|
+
const neverAction = input.action;
|
|
4597
|
+
throw new Error(`Unsupported action: ${neverAction}`);
|
|
4598
|
+
}
|
|
4599
|
+
}
|
|
4600
|
+
} catch (error) {
|
|
4601
|
+
return {
|
|
4602
|
+
action: input.action,
|
|
4603
|
+
selector,
|
|
4604
|
+
ref: normalizedRef,
|
|
4605
|
+
success: false,
|
|
4606
|
+
reason: "browser_error",
|
|
4607
|
+
message: error.message,
|
|
4608
|
+
stale,
|
|
4609
|
+
screenHash: hash,
|
|
4610
|
+
target: refMatch?.target
|
|
4611
|
+
};
|
|
4612
|
+
}
|
|
4613
|
+
return {
|
|
4614
|
+
action: input.action,
|
|
4615
|
+
selector,
|
|
4616
|
+
ref: normalizedRef,
|
|
4617
|
+
success: true,
|
|
4618
|
+
stale,
|
|
4619
|
+
screenHash: hash,
|
|
4620
|
+
target: refMatch?.target
|
|
4621
|
+
};
|
|
4622
|
+
}
|
|
4623
|
+
function matchUrlPattern(url, pattern) {
|
|
4624
|
+
if (pattern.includes("*")) {
|
|
4625
|
+
const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
4626
|
+
return new RegExp(`^${escaped}$`).test(url);
|
|
4627
|
+
}
|
|
4628
|
+
return url.includes(pattern);
|
|
4629
|
+
}
|
|
4630
|
+
async function pollUntil(timeout, pollInterval, predicate) {
|
|
4631
|
+
const startedAt = Date.now();
|
|
4632
|
+
while (Date.now() - startedAt <= timeout) {
|
|
4633
|
+
if (await predicate()) return true;
|
|
4634
|
+
await new Promise((resolve9) => setTimeout(resolve9, pollInterval));
|
|
4635
|
+
}
|
|
4636
|
+
return false;
|
|
4637
|
+
}
|
|
4638
|
+
async function waitForPage(page, input) {
|
|
4639
|
+
const timeout = input.timeout ?? 3e4;
|
|
4640
|
+
const pollInterval = input.pollInterval ?? 200;
|
|
4641
|
+
const startedAt = Date.now();
|
|
4642
|
+
try {
|
|
4643
|
+
if (input.selector) {
|
|
4644
|
+
const state = input.state ?? "visible";
|
|
4645
|
+
await page.locator(input.selector).first().waitFor({ state, timeout });
|
|
4646
|
+
return { success: true, matched: "selector", timeout, elapsed: Date.now() - startedAt };
|
|
4647
|
+
}
|
|
4648
|
+
if (input.text) {
|
|
4649
|
+
await page.getByText(input.text).first().waitFor({ state: "visible", timeout });
|
|
4650
|
+
return { success: true, matched: "text", timeout, elapsed: Date.now() - startedAt };
|
|
4651
|
+
}
|
|
4652
|
+
if (input.url) {
|
|
4653
|
+
const matched = await pollUntil(timeout, pollInterval, async () => matchUrlPattern(page.url(), input.url));
|
|
4654
|
+
return {
|
|
4655
|
+
success: matched,
|
|
4656
|
+
matched: "url",
|
|
4657
|
+
timeout,
|
|
4658
|
+
elapsed: Date.now() - startedAt,
|
|
4659
|
+
...matched ? {} : { message: `Timed out waiting for URL pattern: ${input.url}` }
|
|
4660
|
+
};
|
|
4661
|
+
}
|
|
4662
|
+
if (input.load) {
|
|
4663
|
+
await page.waitForLoadState(input.load, timeout);
|
|
4664
|
+
return { success: true, matched: "load", timeout, elapsed: Date.now() - startedAt };
|
|
4665
|
+
}
|
|
4666
|
+
if (input.fn) {
|
|
4667
|
+
await page.waitForFunction(input.fn, void 0, { timeout });
|
|
4668
|
+
return { success: true, matched: "fn", timeout, elapsed: Date.now() - startedAt };
|
|
4669
|
+
}
|
|
4670
|
+
if (input.screenHashChanged) {
|
|
4671
|
+
let screenHash = await getPageScreenHash(page);
|
|
4672
|
+
const matched = await pollUntil(timeout, pollInterval, async () => {
|
|
4673
|
+
screenHash = await getPageScreenHash(page);
|
|
4674
|
+
return screenHash !== input.screenHashChanged;
|
|
4675
|
+
});
|
|
4676
|
+
return {
|
|
4677
|
+
success: matched,
|
|
4678
|
+
matched: "screenHashChanged",
|
|
4679
|
+
timeout,
|
|
4680
|
+
elapsed: Date.now() - startedAt,
|
|
4681
|
+
screenHash,
|
|
4682
|
+
...matched ? {} : { message: `Timed out waiting for screen hash to change: ${input.screenHashChanged}` }
|
|
4683
|
+
};
|
|
4684
|
+
}
|
|
4685
|
+
} catch (error) {
|
|
4686
|
+
return {
|
|
4687
|
+
success: false,
|
|
4688
|
+
matched: input.selector ? "selector" : input.text ? "text" : input.url ? "url" : input.load ? "load" : input.fn ? "fn" : "screenHashChanged",
|
|
4689
|
+
timeout,
|
|
4690
|
+
elapsed: Date.now() - startedAt,
|
|
4691
|
+
message: error.message
|
|
4692
|
+
};
|
|
4693
|
+
}
|
|
4694
|
+
return {
|
|
4695
|
+
success: false,
|
|
4696
|
+
matched: "selector",
|
|
4697
|
+
timeout,
|
|
4698
|
+
elapsed: Date.now() - startedAt,
|
|
4699
|
+
message: "Provide one wait predicate: selector, text, url, load, fn, or screenHashChanged."
|
|
4700
|
+
};
|
|
4701
|
+
}
|
|
4702
|
+
|
|
4274
4703
|
// src/commands/snapshot.ts
|
|
4275
4704
|
var snapshotCommand = registerCommand({
|
|
4276
4705
|
name: "snapshot",
|
|
@@ -4280,7 +4709,14 @@ var snapshotCommand = registerCommand({
|
|
|
4280
4709
|
parameters: z21.object({
|
|
4281
4710
|
type: z21.enum(["aria", "text", "dom", "all"]).default("aria").describe("Snapshot type: aria (accessibility tree), text (visible text), dom (element summary), all (combined)"),
|
|
4282
4711
|
selector: z21.string().optional().describe("Scope to a specific element"),
|
|
4283
|
-
depth: z21.number().optional().default(6).describe("Max depth for DOM/aria tree")
|
|
4712
|
+
depth: z21.number().optional().default(6).describe("Max depth for DOM/aria tree"),
|
|
4713
|
+
interactive: z21.boolean().optional().default(false).describe("Return interactive agent refs only"),
|
|
4714
|
+
interactiveOnly: z21.boolean().optional().default(false).describe("Alias for interactive"),
|
|
4715
|
+
i: z21.boolean().optional().default(false).describe("Short alias for interactive"),
|
|
4716
|
+
compact: z21.boolean().optional().default(false).describe("Include compact agent-browser style snapshot text"),
|
|
4717
|
+
c: z21.boolean().optional().default(false).describe("Short alias for compact"),
|
|
4718
|
+
selectors: z21.boolean().optional().default(false).describe("Include ref to CSS selector map"),
|
|
4719
|
+
all: z21.boolean().optional().default(false).describe("Include hidden interactive targets when using interactive snapshot")
|
|
4284
4720
|
}),
|
|
4285
4721
|
result: z21.object({
|
|
4286
4722
|
url: z21.string(),
|
|
@@ -4293,6 +4729,18 @@ var snapshotCommand = registerCommand({
|
|
|
4293
4729
|
const page = ctx.page;
|
|
4294
4730
|
const url = page.url();
|
|
4295
4731
|
const title = await page.title().catch(() => "");
|
|
4732
|
+
if (p.interactive || p.interactiveOnly || p.i || p.compact || p.c || p.selectors) {
|
|
4733
|
+
const observation = await observePage(page, ctx.sessionId, {
|
|
4734
|
+
includeHidden: p.all
|
|
4735
|
+
});
|
|
4736
|
+
if (p.selectors) observation.selectors = buildSelectorMap(observation);
|
|
4737
|
+
if (p.compact || p.c || p.interactive || p.interactiveOnly || p.i) {
|
|
4738
|
+
observation.compact = formatObservationCompact(observation, { selectors: p.selectors });
|
|
4739
|
+
}
|
|
4740
|
+
return ok20(observation, [
|
|
4741
|
+
`refs refreshed for ${observation.targets.length} targets; use click @e1 or fill @e2 "text"`
|
|
4742
|
+
]);
|
|
4743
|
+
}
|
|
4296
4744
|
if (p.type === "aria") {
|
|
4297
4745
|
const aria = await captureAriaSnapshot(page, p.selector, p.depth);
|
|
4298
4746
|
const tips = await buildRefTips(page, aria);
|
|
@@ -4343,10 +4791,10 @@ async function buildRefTips(page, aria) {
|
|
|
4343
4791
|
return [];
|
|
4344
4792
|
}
|
|
4345
4793
|
}
|
|
4346
|
-
async function captureAriaSnapshot(page, selector,
|
|
4794
|
+
async function captureAriaSnapshot(page, selector, _depth) {
|
|
4347
4795
|
try {
|
|
4348
4796
|
const locator = selector ? page.locator(selector).first() : page.locator("body");
|
|
4349
|
-
return await locator.ariaSnapshot(
|
|
4797
|
+
return await locator.ariaSnapshot();
|
|
4350
4798
|
} catch {
|
|
4351
4799
|
try {
|
|
4352
4800
|
return await page.locator("body").ariaSnapshot();
|
|
@@ -4357,7 +4805,7 @@ async function captureAriaSnapshot(page, selector, depth) {
|
|
|
4357
4805
|
}
|
|
4358
4806
|
async function captureTextSnapshot(page, selector) {
|
|
4359
4807
|
if (selector) {
|
|
4360
|
-
return await page.locator(selector).first().innerText(
|
|
4808
|
+
return await page.locator(selector).first().innerText().catch(() => "");
|
|
4361
4809
|
}
|
|
4362
4810
|
return await page.evaluate(() => document.body?.innerText || "").catch(() => "");
|
|
4363
4811
|
}
|
|
@@ -4393,22 +4841,108 @@ async function captureDomSnapshot(page, selector, maxDepth) {
|
|
|
4393
4841
|
).catch(() => ({ tag: "error" }));
|
|
4394
4842
|
}
|
|
4395
4843
|
|
|
4396
|
-
// src/commands/
|
|
4844
|
+
// src/commands/agent.ts
|
|
4397
4845
|
import { z as z22 } from "zod";
|
|
4398
|
-
import { ok as ok21
|
|
4399
|
-
var
|
|
4400
|
-
|
|
4401
|
-
|
|
4402
|
-
|
|
4846
|
+
import { ok as ok21 } from "@dyyz1993/xcli-core";
|
|
4847
|
+
var observeCommand = registerCommand({
|
|
4848
|
+
name: "observe",
|
|
4849
|
+
description: "Observe the current page as structured agent targets with session refs",
|
|
4850
|
+
scope: "page",
|
|
4851
|
+
parameters: z22.object({
|
|
4852
|
+
includeHidden: z22.boolean().optional().default(false).describe("Include hidden elements in the target list"),
|
|
4853
|
+
limit: z22.number().int().positive().max(300).optional().default(80).describe("Maximum number of targets to return"),
|
|
4854
|
+
compact: z22.boolean().optional().default(false).describe("Include compact agent-browser style snapshot text"),
|
|
4855
|
+
selectors: z22.boolean().optional().default(false).describe("Include ref to stable CSS selector map")
|
|
4856
|
+
}),
|
|
4857
|
+
handler: async (p, ctx) => {
|
|
4858
|
+
const observation = await observePage(ctx.page, ctx.sessionId, {
|
|
4859
|
+
includeHidden: p.includeHidden,
|
|
4860
|
+
limit: p.limit
|
|
4861
|
+
});
|
|
4862
|
+
if (p.selectors) observation.selectors = buildSelectorMap(observation);
|
|
4863
|
+
if (p.compact) observation.compact = formatObservationCompact(observation, { selectors: p.selectors });
|
|
4864
|
+
return ok21(observation, [
|
|
4865
|
+
`refs refreshed for ${observation.targets.length} targets; use act --ref @e1 --action click or click @e1`
|
|
4866
|
+
]);
|
|
4867
|
+
}
|
|
4868
|
+
});
|
|
4869
|
+
var actCommand = registerCommand({
|
|
4870
|
+
name: "act",
|
|
4871
|
+
description: "Perform an agent action using an observe ref or explicit selector",
|
|
4872
|
+
scope: "element",
|
|
4873
|
+
selectorParams: ["selector"],
|
|
4874
|
+
parameters: z22.object({
|
|
4875
|
+
action: z22.enum(["click", "fill", "type", "press", "select", "check", "hover"]).default("click"),
|
|
4876
|
+
ref: z22.string().optional().describe("Session-scoped ref returned by observe, such as e1"),
|
|
4877
|
+
selector: z22.string().optional().describe("CSS selector fallback when no ref is available"),
|
|
4878
|
+
value: z22.string().optional().describe("Value for fill/type/select"),
|
|
4879
|
+
key: z22.string().optional().describe("Key for press"),
|
|
4880
|
+
force: z22.boolean().optional().default(false).describe("Bypass actionability checks"),
|
|
4881
|
+
timeout: z22.number().optional().default(1e4).describe("Playwright action timeout in milliseconds")
|
|
4882
|
+
}).refine((p) => !!p.ref || !!p.selector, {
|
|
4883
|
+
message: "Either ref or selector is required"
|
|
4884
|
+
}),
|
|
4885
|
+
handler: async (p, ctx) => {
|
|
4886
|
+
const result = await actOnPage(ctx.page, ctx.sessionId, { ...p });
|
|
4887
|
+
if (!result.success) {
|
|
4888
|
+
return {
|
|
4889
|
+
success: false,
|
|
4890
|
+
data: result,
|
|
4891
|
+
message: result.message || result.reason || "Action failed",
|
|
4892
|
+
tips: result.stale ? ["run observe again to refresh refs"] : []
|
|
4893
|
+
};
|
|
4894
|
+
}
|
|
4895
|
+
return ok21(result, result.stale ? ["ref screen hash changed; run observe if the next action is uncertain"] : []);
|
|
4896
|
+
}
|
|
4897
|
+
});
|
|
4898
|
+
var waitForCommand = registerCommand({
|
|
4899
|
+
name: "waitFor",
|
|
4900
|
+
description: "Wait for agent predicates such as text, URL, load state, selector state, or screen hash changes",
|
|
4901
|
+
scope: "page",
|
|
4902
|
+
selectorParams: ["selector"],
|
|
4903
|
+
parameters: z22.object({
|
|
4904
|
+
selector: z22.string().optional().describe("CSS selector or observe ref to wait for"),
|
|
4905
|
+
state: z22.enum(["attached", "detached", "visible", "hidden"]).optional().default("visible"),
|
|
4906
|
+
text: z22.string().optional().describe("Visible text to wait for"),
|
|
4907
|
+
url: z22.string().optional().describe("URL substring or glob pattern to wait for"),
|
|
4908
|
+
load: z22.enum(["load", "domcontentloaded", "networkidle"]).optional().describe("Load state to wait for"),
|
|
4909
|
+
fn: z22.string().optional().describe("JavaScript predicate to wait for"),
|
|
4910
|
+
screenHashChanged: z22.string().optional().describe("Previous screenHash from observe"),
|
|
4911
|
+
timeout: z22.number().optional().default(3e4),
|
|
4912
|
+
pollInterval: z22.number().optional().default(200)
|
|
4913
|
+
}).refine((p) => [p.selector, p.text, p.url, p.load, p.fn, p.screenHashChanged].filter(Boolean).length === 1, {
|
|
4914
|
+
message: "Provide exactly one wait predicate: selector, text, url, load, fn, or screenHashChanged"
|
|
4915
|
+
}),
|
|
4916
|
+
handler: async (p, ctx) => {
|
|
4917
|
+
const result = await waitForPage(ctx.page, { ...p });
|
|
4918
|
+
if (!result.success) {
|
|
4919
|
+
return {
|
|
4920
|
+
success: false,
|
|
4921
|
+
data: result,
|
|
4922
|
+
message: result.message || `Timed out waiting for ${result.matched}`,
|
|
4923
|
+
tips: []
|
|
4924
|
+
};
|
|
4925
|
+
}
|
|
4926
|
+
return ok21(result);
|
|
4927
|
+
}
|
|
4928
|
+
});
|
|
4929
|
+
|
|
4930
|
+
// src/commands/tab.ts
|
|
4931
|
+
import { z as z23 } from "zod";
|
|
4932
|
+
import { ok as ok22, fail as fail5 } from "@dyyz1993/xcli-core";
|
|
4933
|
+
var TabParams = z23.object({
|
|
4934
|
+
subcommand: z23.enum(["list", "new", "close", "switch"]),
|
|
4935
|
+
url: z23.string().optional(),
|
|
4936
|
+
index: z23.number().int().min(0).optional()
|
|
4403
4937
|
});
|
|
4404
4938
|
var tabCommand = registerCommand({
|
|
4405
4939
|
name: "tab",
|
|
4406
4940
|
description: "Manage browser tabs: list, new, close, switch",
|
|
4407
4941
|
scope: "page",
|
|
4408
4942
|
parameters: TabParams,
|
|
4409
|
-
result:
|
|
4410
|
-
success:
|
|
4411
|
-
data:
|
|
4943
|
+
result: z23.object({
|
|
4944
|
+
success: z23.boolean(),
|
|
4945
|
+
data: z23.unknown()
|
|
4412
4946
|
}),
|
|
4413
4947
|
handler: async (p, ctx) => {
|
|
4414
4948
|
const pages = ctx.browserContext.pages();
|
|
@@ -4448,7 +4982,7 @@ function handleList(pages, ctx) {
|
|
|
4448
4982
|
active: i === currentIndex
|
|
4449
4983
|
};
|
|
4450
4984
|
});
|
|
4451
|
-
return
|
|
4985
|
+
return ok22({ tabs, total: tabs.length, activeIndex: currentIndex });
|
|
4452
4986
|
}
|
|
4453
4987
|
async function handleNew(p, _pages, ctx) {
|
|
4454
4988
|
const newPage = await ctx.browserContext.newPage();
|
|
@@ -4470,7 +5004,7 @@ async function handleNew(p, _pages, ctx) {
|
|
|
4470
5004
|
const title = await newPage.title().catch(() => "");
|
|
4471
5005
|
const allPages = ctx.browserContext.pages();
|
|
4472
5006
|
const newIndex = allPages.indexOf(newPage);
|
|
4473
|
-
return
|
|
5007
|
+
return ok22({
|
|
4474
5008
|
index: newIndex >= 0 ? newIndex : allPages.length - 1,
|
|
4475
5009
|
url: newPage.url(),
|
|
4476
5010
|
title,
|
|
@@ -4498,7 +5032,7 @@ async function handleClose(p, pages, ctx) {
|
|
|
4498
5032
|
}
|
|
4499
5033
|
ctx.page = newActivePage;
|
|
4500
5034
|
}
|
|
4501
|
-
return
|
|
5035
|
+
return ok22({
|
|
4502
5036
|
closedIndex: closeIndex,
|
|
4503
5037
|
total: remainingPages.length,
|
|
4504
5038
|
activeIndex: isActivePage ? closeIndex < remainingPages.length ? closeIndex : remainingPages.length - 1 : pages.indexOf(ctx.page)
|
|
@@ -4520,7 +5054,7 @@ async function handleSwitch(p, pages, ctx) {
|
|
|
4520
5054
|
}
|
|
4521
5055
|
ctx.page = targetPage;
|
|
4522
5056
|
const title = await targetPage.title().catch(() => "");
|
|
4523
|
-
return
|
|
5057
|
+
return ok22({
|
|
4524
5058
|
index: p.index,
|
|
4525
5059
|
url: targetPage.url(),
|
|
4526
5060
|
title,
|
|
@@ -4529,8 +5063,8 @@ async function handleSwitch(p, pages, ctx) {
|
|
|
4529
5063
|
}
|
|
4530
5064
|
|
|
4531
5065
|
// src/commands/addinitscript.ts
|
|
4532
|
-
import { z as
|
|
4533
|
-
import { ok as
|
|
5066
|
+
import { z as z24 } from "zod";
|
|
5067
|
+
import { ok as ok23 } from "@dyyz1993/xcli-core";
|
|
4534
5068
|
import { readFileSync as readFileSync2 } from "fs";
|
|
4535
5069
|
|
|
4536
5070
|
// src/chain-parser.ts
|
|
@@ -4708,7 +5242,7 @@ function parseCommandArgs(name, args) {
|
|
|
4708
5242
|
} else {
|
|
4709
5243
|
if (positionalIndex < positionalKeys.length) {
|
|
4710
5244
|
const isLast = positionalIndex === positionalKeys.length - 1;
|
|
4711
|
-
if (isLast && name === "eval") {
|
|
5245
|
+
if (isLast && (name === "eval" || name === "find")) {
|
|
4712
5246
|
const remaining = args.slice(i).map(unquote2).join(" ");
|
|
4713
5247
|
params[positionalKeys[positionalIndex]] = remaining;
|
|
4714
5248
|
break;
|
|
@@ -4758,7 +5292,7 @@ var commandDefCache = {
|
|
|
4758
5292
|
frames: { positional: [] },
|
|
4759
5293
|
frame: { positional: ["selector"] },
|
|
4760
5294
|
actions: { positional: ["url"] },
|
|
4761
|
-
find: { positional: ["strategy", "value"] },
|
|
5295
|
+
find: { positional: ["strategy", "value", "operation"] },
|
|
4762
5296
|
addinitscript: { positional: ["script"] },
|
|
4763
5297
|
tab: { positional: ["subcommand"] }
|
|
4764
5298
|
};
|
|
@@ -4770,14 +5304,14 @@ function registerCommandDefinition(name, positional) {
|
|
|
4770
5304
|
}
|
|
4771
5305
|
|
|
4772
5306
|
// src/commands/addinitscript.ts
|
|
4773
|
-
var InitScriptParams =
|
|
4774
|
-
script:
|
|
4775
|
-
file:
|
|
4776
|
-
stdin:
|
|
4777
|
-
name:
|
|
4778
|
-
list:
|
|
4779
|
-
remove:
|
|
4780
|
-
base64:
|
|
5307
|
+
var InitScriptParams = z24.object({
|
|
5308
|
+
script: z24.string().optional(),
|
|
5309
|
+
file: z24.string().optional(),
|
|
5310
|
+
stdin: z24.boolean().optional(),
|
|
5311
|
+
name: z24.string().optional(),
|
|
5312
|
+
list: z24.boolean().optional(),
|
|
5313
|
+
remove: z24.string().optional(),
|
|
5314
|
+
base64: z24.string().optional()
|
|
4781
5315
|
});
|
|
4782
5316
|
var registeredScripts = /* @__PURE__ */ new Map();
|
|
4783
5317
|
function resolveScriptContent(params) {
|
|
@@ -4818,15 +5352,15 @@ var addInitScriptCommand = registerCommand({
|
|
|
4818
5352
|
size: content2.length,
|
|
4819
5353
|
preview: content2.slice(0, 80)
|
|
4820
5354
|
}));
|
|
4821
|
-
return
|
|
5355
|
+
return ok23({ scripts });
|
|
4822
5356
|
}
|
|
4823
5357
|
if (params.remove) {
|
|
4824
5358
|
const existed = registeredScripts.delete(params.remove);
|
|
4825
|
-
return
|
|
5359
|
+
return ok23({ removed: params.remove, existed });
|
|
4826
5360
|
}
|
|
4827
5361
|
let content = params.stdin ? await readStdin() : resolveScriptContent(params);
|
|
4828
5362
|
if (!content) {
|
|
4829
|
-
return
|
|
5363
|
+
return ok23({ error: "No script content provided. Use --script, --file, --stdin, or --base64" });
|
|
4830
5364
|
}
|
|
4831
5365
|
const scriptName = params.name ?? `script-${Date.now()}`;
|
|
4832
5366
|
registeredScripts.set(scriptName, content);
|
|
@@ -4834,74 +5368,134 @@ var addInitScriptCommand = registerCommand({
|
|
|
4834
5368
|
try {
|
|
4835
5369
|
await ctx.page.evaluate(content);
|
|
4836
5370
|
} catch {
|
|
4837
|
-
return
|
|
5371
|
+
return ok23({
|
|
4838
5372
|
registered: scriptName,
|
|
4839
5373
|
hint: "Script registered for future page loads; immediate execution skipped (page may not be ready)"
|
|
4840
5374
|
});
|
|
4841
5375
|
}
|
|
4842
|
-
return
|
|
5376
|
+
return ok23({ registered: scriptName, executedImmediately: true });
|
|
4843
5377
|
}
|
|
4844
5378
|
});
|
|
4845
5379
|
registerCommandDefinition("addinitscript", ["script"]);
|
|
4846
5380
|
|
|
4847
5381
|
// src/commands/find.ts
|
|
4848
|
-
import { z as
|
|
4849
|
-
import { ok as
|
|
5382
|
+
import { z as z25 } from "zod";
|
|
5383
|
+
import { ok as ok24, fail as fail6 } from "@dyyz1993/xcli-core";
|
|
5384
|
+
var actionSchema2 = z25.enum(["click", "fill", "type", "select", "hover", "check"]);
|
|
4850
5385
|
var findCommand = registerCommand({
|
|
4851
5386
|
name: "find",
|
|
4852
5387
|
description: "Find elements by semantic strategy (text/role/label/placeholder/testid) and optionally perform an action",
|
|
4853
5388
|
scope: "page",
|
|
4854
|
-
parameters:
|
|
4855
|
-
strategy:
|
|
4856
|
-
value:
|
|
4857
|
-
name:
|
|
4858
|
-
exact:
|
|
4859
|
-
|
|
4860
|
-
|
|
4861
|
-
|
|
4862
|
-
|
|
4863
|
-
|
|
5389
|
+
parameters: z25.object({
|
|
5390
|
+
strategy: z25.enum(["text", "role", "label", "placeholder", "testid", "alt", "title", "first", "last", "nth"]),
|
|
5391
|
+
value: z25.string(),
|
|
5392
|
+
name: z25.string().optional(),
|
|
5393
|
+
exact: z25.boolean().optional().default(false),
|
|
5394
|
+
operation: z25.string().optional().describe('Trailing operation syntax, e.g. click, fill "text", type "text"'),
|
|
5395
|
+
action: actionSchema2.optional().describe("Action to perform when not using trailing operation syntax"),
|
|
5396
|
+
actionValue: z25.string().optional().describe("Value for fill/type/select when using action"),
|
|
5397
|
+
index: z25.number().int().optional().describe("Index for nth strategy"),
|
|
5398
|
+
click: z25.boolean().optional().default(false),
|
|
5399
|
+
fill: z25.string().optional(),
|
|
5400
|
+
type: z25.string().optional(),
|
|
5401
|
+
select: z25.string().optional(),
|
|
5402
|
+
hover: z25.boolean().optional().default(false),
|
|
5403
|
+
check: z25.boolean().optional().default(false),
|
|
5404
|
+
timeout: z25.number().optional().default(1e4)
|
|
4864
5405
|
}),
|
|
4865
|
-
result:
|
|
4866
|
-
matched:
|
|
4867
|
-
selector:
|
|
4868
|
-
action:
|
|
5406
|
+
result: z25.object({
|
|
5407
|
+
matched: z25.number(),
|
|
5408
|
+
selector: z25.string(),
|
|
5409
|
+
action: z25.string().optional()
|
|
4869
5410
|
}),
|
|
4870
5411
|
handler: async (p, ctx) => {
|
|
4871
5412
|
const page = ctx.page;
|
|
4872
|
-
const
|
|
5413
|
+
const normalized = normalizeFindParams({ ...p });
|
|
5414
|
+
const parsedOperation = parseOperation(normalized.operation);
|
|
5415
|
+
const actionName = parsedOperation.action || p.action || inferLegacyAction(p);
|
|
5416
|
+
const actionValue = parsedOperation.value ?? p.actionValue ?? p.fill ?? p.type ?? p.select;
|
|
5417
|
+
const locator = buildLocator(page, normalized.strategy, normalized.value, {
|
|
4873
5418
|
name: p.name,
|
|
4874
|
-
exact: p.exact
|
|
5419
|
+
exact: p.exact,
|
|
5420
|
+
index: normalized.index
|
|
4875
5421
|
});
|
|
4876
5422
|
const count = await locator.count();
|
|
4877
5423
|
if (count === 0) {
|
|
4878
5424
|
return fail6(`No element found with ${p.strategy}="${p.value}"`);
|
|
4879
5425
|
}
|
|
4880
5426
|
const tips = [];
|
|
4881
|
-
const target = locator.
|
|
5427
|
+
const target = selectTarget(locator, p.strategy);
|
|
4882
5428
|
if (count > 1) {
|
|
4883
|
-
tips.push(`\u26A0\uFE0F Matched ${count} elements,
|
|
5429
|
+
tips.push(`\u26A0\uFE0F Matched ${count} elements, used first match. Use 'find nth <index> ${normalized.strategy} "${normalized.value}" ${actionName || "click"}' for a specific match.`);
|
|
4884
5430
|
}
|
|
4885
|
-
const selector = describeSelector(
|
|
4886
|
-
|
|
4887
|
-
if (p.click) {
|
|
5431
|
+
const selector = describeSelector(normalized.strategy, normalized.value, p.name);
|
|
5432
|
+
if (actionName === "click") {
|
|
4888
5433
|
await target.click({ timeout: p.timeout, force: true });
|
|
4889
|
-
action
|
|
4890
|
-
} else if (
|
|
4891
|
-
|
|
4892
|
-
|
|
4893
|
-
|
|
4894
|
-
|
|
4895
|
-
|
|
4896
|
-
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
|
|
4900
|
-
|
|
4901
|
-
|
|
4902
|
-
|
|
5434
|
+
return okWithTips({ matched: count, selector, action: "click" }, tips);
|
|
5435
|
+
} else if (actionName === "fill") {
|
|
5436
|
+
if (actionValue === void 0) return fail6("find fill requires a value");
|
|
5437
|
+
await target.fill(actionValue, { timeout: p.timeout, force: true });
|
|
5438
|
+
return okWithTips({ matched: count, selector, action: `fill("${actionValue}")` }, tips);
|
|
5439
|
+
} else if (actionName === "type") {
|
|
5440
|
+
if (actionValue === void 0) return fail6("find type requires a value");
|
|
5441
|
+
await target.type(actionValue, { delay: 10, timeout: p.timeout });
|
|
5442
|
+
return okWithTips({ matched: count, selector, action: `type("${actionValue}")` }, tips);
|
|
5443
|
+
} else if (actionName === "select") {
|
|
5444
|
+
if (actionValue === void 0) return fail6("find select requires a value");
|
|
5445
|
+
await target.selectOption(actionValue);
|
|
5446
|
+
return okWithTips({ matched: count, selector, action: `select("${actionValue}")` }, tips);
|
|
5447
|
+
} else if (actionName === "hover") {
|
|
5448
|
+
await target.hover({ timeout: p.timeout, force: true });
|
|
5449
|
+
return okWithTips({ matched: count, selector, action: "hover" }, tips);
|
|
5450
|
+
} else if (actionName === "check") {
|
|
5451
|
+
await target.check({ timeout: p.timeout });
|
|
5452
|
+
return okWithTips({ matched: count, selector, action: "check" }, tips);
|
|
5453
|
+
}
|
|
5454
|
+
return okWithTips({ matched: count, selector }, tips);
|
|
4903
5455
|
}
|
|
4904
5456
|
});
|
|
5457
|
+
function okWithTips(data, tips) {
|
|
5458
|
+
const result = ok24(data);
|
|
5459
|
+
if (tips.length > 0) result.tips = tips;
|
|
5460
|
+
return result;
|
|
5461
|
+
}
|
|
5462
|
+
function parseOperation(operation) {
|
|
5463
|
+
if (!operation) return {};
|
|
5464
|
+
const match = operation.trim().match(/^(\S+)(?:\s+([\s\S]+))?$/);
|
|
5465
|
+
if (!match) return {};
|
|
5466
|
+
const maybeAction = match[1];
|
|
5467
|
+
const parsed = actionSchema2.safeParse(maybeAction);
|
|
5468
|
+
if (!parsed.success) return {};
|
|
5469
|
+
const rawValue = match[2];
|
|
5470
|
+
const value = rawValue?.replace(/^["']|["']$/g, "");
|
|
5471
|
+
return { action: parsed.data, ...value !== void 0 ? { value } : {} };
|
|
5472
|
+
}
|
|
5473
|
+
function normalizeFindParams(p) {
|
|
5474
|
+
if (p.strategy !== "nth") return { strategy: p.strategy, value: p.value, operation: p.operation, index: p.index };
|
|
5475
|
+
const parsedIndex = Number(p.value);
|
|
5476
|
+
if (!Number.isInteger(parsedIndex) || !p.operation) {
|
|
5477
|
+
return { strategy: p.strategy, value: p.value, operation: p.operation, index: p.index };
|
|
5478
|
+
}
|
|
5479
|
+
const match = p.operation.trim().match(/^(\S+)(?:\s+([\s\S]+))?$/);
|
|
5480
|
+
if (!match) {
|
|
5481
|
+
return { strategy: p.strategy, value: p.value, operation: p.operation, index: parsedIndex };
|
|
5482
|
+
}
|
|
5483
|
+
return {
|
|
5484
|
+
strategy: p.strategy,
|
|
5485
|
+
value: match[1].replace(/^["']|["']$/g, ""),
|
|
5486
|
+
...match[2] ? { operation: match[2] } : {},
|
|
5487
|
+
index: parsedIndex
|
|
5488
|
+
};
|
|
5489
|
+
}
|
|
5490
|
+
function inferLegacyAction(p) {
|
|
5491
|
+
if (p.click) return "click";
|
|
5492
|
+
if (p.fill !== void 0) return "fill";
|
|
5493
|
+
if (p.type !== void 0) return "type";
|
|
5494
|
+
if (p.select !== void 0) return "select";
|
|
5495
|
+
if (p.hover) return "hover";
|
|
5496
|
+
if (p.check) return "check";
|
|
5497
|
+
return void 0;
|
|
5498
|
+
}
|
|
4905
5499
|
function buildLocator(page, strategy, value, opts) {
|
|
4906
5500
|
switch (strategy) {
|
|
4907
5501
|
case "text":
|
|
@@ -4914,10 +5508,24 @@ function buildLocator(page, strategy, value, opts) {
|
|
|
4914
5508
|
return page.getByPlaceholder(value, { exact: opts.exact });
|
|
4915
5509
|
case "testid":
|
|
4916
5510
|
return page.getByTestId(value);
|
|
5511
|
+
case "alt":
|
|
5512
|
+
return page.getByAltText(value, { exact: opts.exact });
|
|
5513
|
+
case "title":
|
|
5514
|
+
return page.getByTitle(value, { exact: opts.exact });
|
|
5515
|
+
case "first":
|
|
5516
|
+
return page.locator(value).first();
|
|
5517
|
+
case "last":
|
|
5518
|
+
return page.locator(value).last();
|
|
5519
|
+
case "nth":
|
|
5520
|
+
return page.locator(value).nth(opts.index ?? 0);
|
|
4917
5521
|
default:
|
|
4918
5522
|
return page.getByText(value, { exact: opts.exact });
|
|
4919
5523
|
}
|
|
4920
5524
|
}
|
|
5525
|
+
function selectTarget(locator, strategy) {
|
|
5526
|
+
if (strategy === "first" || strategy === "last" || strategy === "nth") return locator;
|
|
5527
|
+
return locator.first();
|
|
5528
|
+
}
|
|
4921
5529
|
function describeSelector(strategy, value, name) {
|
|
4922
5530
|
switch (strategy) {
|
|
4923
5531
|
case "role":
|
|
@@ -4930,6 +5538,16 @@ function describeSelector(strategy, value, name) {
|
|
|
4930
5538
|
return `getByPlaceholder("${value}")`;
|
|
4931
5539
|
case "testid":
|
|
4932
5540
|
return `getByTestId("${value}")`;
|
|
5541
|
+
case "alt":
|
|
5542
|
+
return `getByAltText("${value}")`;
|
|
5543
|
+
case "title":
|
|
5544
|
+
return `getByTitle("${value}")`;
|
|
5545
|
+
case "first":
|
|
5546
|
+
return `first("${value}")`;
|
|
5547
|
+
case "last":
|
|
5548
|
+
return `last("${value}")`;
|
|
5549
|
+
case "nth":
|
|
5550
|
+
return `nth("${value}")`;
|
|
4933
5551
|
default:
|
|
4934
5552
|
return `${strategy}("${value}")`;
|
|
4935
5553
|
}
|
|
@@ -5052,7 +5670,7 @@ async function detectCaptcha2(page) {
|
|
|
5052
5670
|
}
|
|
5053
5671
|
async function detectWarningText(page) {
|
|
5054
5672
|
try {
|
|
5055
|
-
const pageText = await page.textContent("body"
|
|
5673
|
+
const pageText = await page.textContent("body").catch(() => "") || "";
|
|
5056
5674
|
const lowerText = pageText.toLowerCase();
|
|
5057
5675
|
for (const { text, severity } of WARNING_TEXTS) {
|
|
5058
5676
|
if (lowerText.includes(text.toLowerCase())) {
|
|
@@ -5183,8 +5801,8 @@ function formatDetectionMessage(result) {
|
|
|
5183
5801
|
}
|
|
5184
5802
|
|
|
5185
5803
|
// src/commands/promo.ts
|
|
5186
|
-
import { z as
|
|
5187
|
-
import { ok as
|
|
5804
|
+
import { z as z26 } from "zod";
|
|
5805
|
+
import { ok as ok25 } from "@dyyz1993/xcli-core";
|
|
5188
5806
|
import { existsSync as existsSync2, readFileSync as readFileSync8 } from "fs";
|
|
5189
5807
|
import { resolve as resolve6 } from "path";
|
|
5190
5808
|
|
|
@@ -5487,14 +6105,14 @@ async function dispatchPromo(config) {
|
|
|
5487
6105
|
}
|
|
5488
6106
|
|
|
5489
6107
|
// src/commands/promo.ts
|
|
5490
|
-
var promoParams =
|
|
5491
|
-
platform:
|
|
5492
|
-
file:
|
|
5493
|
-
tags:
|
|
5494
|
-
title:
|
|
5495
|
-
search:
|
|
5496
|
-
cdpEndpoint:
|
|
5497
|
-
session:
|
|
6108
|
+
var promoParams = z26.object({
|
|
6109
|
+
platform: z26.enum(["devto", "medium", "csdn", "juejin", "quora"]).describe("Target platform for promotion"),
|
|
6110
|
+
file: z26.string().describe("Path to Markdown file to publish"),
|
|
6111
|
+
tags: z26.string().optional().describe("Comma-separated tags"),
|
|
6112
|
+
title: z26.string().optional().describe("Custom title (default: extracted from file first heading)"),
|
|
6113
|
+
search: z26.string().optional().describe("Quora: search query to find questions"),
|
|
6114
|
+
cdpEndpoint: z26.string().optional().describe("CDP endpoint for agent-browser"),
|
|
6115
|
+
session: z26.string().optional().describe("agent-browser session name")
|
|
5498
6116
|
}).refine(
|
|
5499
6117
|
(data) => data.platform !== "quora" || !!data.search,
|
|
5500
6118
|
{ message: "Quora platform requires --search parameter" }
|
|
@@ -5507,17 +6125,17 @@ var promoCommand = registerCommand({
|
|
|
5507
6125
|
description: "Publish promotional articles to various platforms (devto, medium, csdn, juejin, quora)",
|
|
5508
6126
|
scope: "project",
|
|
5509
6127
|
parameters: promoParams,
|
|
5510
|
-
result:
|
|
5511
|
-
success:
|
|
5512
|
-
url:
|
|
5513
|
-
error:
|
|
5514
|
-
platform:
|
|
6128
|
+
result: z26.object({
|
|
6129
|
+
success: z26.boolean(),
|
|
6130
|
+
url: z26.string().optional(),
|
|
6131
|
+
error: z26.string().optional(),
|
|
6132
|
+
platform: z26.string()
|
|
5515
6133
|
}),
|
|
5516
6134
|
handler: async (p, _ctx) => {
|
|
5517
6135
|
const filePath = resolve6(p.file);
|
|
5518
6136
|
const content = readFileSync8(filePath, "utf-8");
|
|
5519
6137
|
if (content.trim().length === 0) {
|
|
5520
|
-
return
|
|
6138
|
+
return ok25({
|
|
5521
6139
|
success: false,
|
|
5522
6140
|
error: `File is empty: ${filePath}`,
|
|
5523
6141
|
platform: p.platform
|
|
@@ -5532,7 +6150,7 @@ var promoCommand = registerCommand({
|
|
|
5532
6150
|
cdpEndpoint: p.cdpEndpoint ?? _ctx.cdpEndpoint,
|
|
5533
6151
|
session: p.session ?? p.platform
|
|
5534
6152
|
});
|
|
5535
|
-
return
|
|
6153
|
+
return ok25(result);
|
|
5536
6154
|
}
|
|
5537
6155
|
});
|
|
5538
6156
|
|
|
@@ -5633,7 +6251,7 @@ import { join as join2 } from "path";
|
|
|
5633
6251
|
import { execSync as execSync6 } from "child_process";
|
|
5634
6252
|
var SHARED_PLUGIN_DEPENDENCIES = {
|
|
5635
6253
|
"zod": "^3.24.0",
|
|
5636
|
-
"@dyyz1993/xcli-core": "^0.
|
|
6254
|
+
"@dyyz1993/xcli-core": "^0.12.1"
|
|
5637
6255
|
};
|
|
5638
6256
|
function ensurePluginDependencies(pluginsDir) {
|
|
5639
6257
|
const zodPath = join2(pluginsDir, "node_modules", "zod");
|
|
@@ -5672,6 +6290,192 @@ function ensurePluginDependencies(pluginsDir) {
|
|
|
5672
6290
|
}
|
|
5673
6291
|
}
|
|
5674
6292
|
|
|
6293
|
+
// src/plugin/contract.ts
|
|
6294
|
+
function buildPluginContract(site) {
|
|
6295
|
+
const commands = site.getAllCommands().map((command) => buildCommandContract(site.getCommand?.(command.name) || command));
|
|
6296
|
+
return {
|
|
6297
|
+
version: 2,
|
|
6298
|
+
plugin: {
|
|
6299
|
+
name: site.name,
|
|
6300
|
+
url: site.url,
|
|
6301
|
+
description: site.config?.description,
|
|
6302
|
+
requiresLogin: site.config?.requiresLogin
|
|
6303
|
+
},
|
|
6304
|
+
commands
|
|
6305
|
+
};
|
|
6306
|
+
}
|
|
6307
|
+
function buildCommandContract(command) {
|
|
6308
|
+
const extension = command.xbrowser || {};
|
|
6309
|
+
const inferredFields = fieldsFromZodObject(command.parameters);
|
|
6310
|
+
const fields = mergeFields(inferredFields, extension.form?.fields || []);
|
|
6311
|
+
const positional = extension.positional || fields.filter((field) => field.positional).map((field) => field.name);
|
|
6312
|
+
const capabilities = extension.capabilities || inferCapabilities(command.scope || "project", command.requiresLogin);
|
|
6313
|
+
const outputSchema = command.result ? summarizeZod(command.result) : void 0;
|
|
6314
|
+
return {
|
|
6315
|
+
name: command.name,
|
|
6316
|
+
description: command.description || "",
|
|
6317
|
+
scope: command.scope || "project",
|
|
6318
|
+
requiresLogin: command.requiresLogin === true,
|
|
6319
|
+
category: extension.category,
|
|
6320
|
+
capabilities,
|
|
6321
|
+
positional,
|
|
6322
|
+
form: {
|
|
6323
|
+
title: extension.form?.title || command.description || command.name,
|
|
6324
|
+
description: extension.form?.description,
|
|
6325
|
+
submitLabel: extension.form?.submitLabel || "Run",
|
|
6326
|
+
fields
|
|
6327
|
+
},
|
|
6328
|
+
output: extension.output || (outputSchema ? { schema: outputSchema } : void 0)
|
|
6329
|
+
};
|
|
6330
|
+
}
|
|
6331
|
+
function fieldsFromZodObject(schema) {
|
|
6332
|
+
const shape = getShape(schema);
|
|
6333
|
+
if (!shape) return [];
|
|
6334
|
+
return Object.entries(shape).map(([name, field]) => fieldFromZod(name, field));
|
|
6335
|
+
}
|
|
6336
|
+
function fieldFromZod(name, schema) {
|
|
6337
|
+
const unwrapped = unwrapZod(schema);
|
|
6338
|
+
const type = zodTypeToContractType(unwrapped.typeName);
|
|
6339
|
+
const enumValues = extractEnumValues(unwrapped.schema);
|
|
6340
|
+
return {
|
|
6341
|
+
name,
|
|
6342
|
+
label: toLabel(name),
|
|
6343
|
+
type,
|
|
6344
|
+
widget: widgetFor(type, enumValues, unwrapped.schema),
|
|
6345
|
+
required: !unwrapped.optional,
|
|
6346
|
+
...unwrapped.description ? { description: unwrapped.description } : {},
|
|
6347
|
+
...unwrapped.defaultValue !== void 0 ? { default: unwrapped.defaultValue } : {},
|
|
6348
|
+
...enumValues ? { enum: enumValues } : {},
|
|
6349
|
+
...type === "array" ? { multiple: true } : {}
|
|
6350
|
+
};
|
|
6351
|
+
}
|
|
6352
|
+
function mergeFields(inferred, overrides) {
|
|
6353
|
+
if (overrides.length === 0) return inferred;
|
|
6354
|
+
const byName = new Map(inferred.map((field) => [field.name, field]));
|
|
6355
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6356
|
+
const merged = [];
|
|
6357
|
+
for (const override of overrides) {
|
|
6358
|
+
if (!override.name) continue;
|
|
6359
|
+
const base = byName.get(override.name) || {
|
|
6360
|
+
name: override.name,
|
|
6361
|
+
label: toLabel(override.name),
|
|
6362
|
+
type: "string",
|
|
6363
|
+
widget: "text",
|
|
6364
|
+
required: false
|
|
6365
|
+
};
|
|
6366
|
+
merged.push({ ...base, ...override, name: override.name });
|
|
6367
|
+
seen.add(override.name);
|
|
6368
|
+
}
|
|
6369
|
+
for (const field of inferred) {
|
|
6370
|
+
if (!seen.has(field.name)) merged.push(field);
|
|
6371
|
+
}
|
|
6372
|
+
return merged;
|
|
6373
|
+
}
|
|
6374
|
+
function inferCapabilities(scope, requiresLogin) {
|
|
6375
|
+
const caps = [];
|
|
6376
|
+
if (scope === "page") caps.push("browser.page");
|
|
6377
|
+
if (scope === "browser") caps.push("browser.context");
|
|
6378
|
+
if (requiresLogin) caps.push("auth.login");
|
|
6379
|
+
return caps;
|
|
6380
|
+
}
|
|
6381
|
+
function getShape(schema) {
|
|
6382
|
+
const zod = schema;
|
|
6383
|
+
const shapeOrFn = zod?.shape ?? zod?._def?.shape;
|
|
6384
|
+
if (!shapeOrFn) return void 0;
|
|
6385
|
+
return typeof shapeOrFn === "function" ? shapeOrFn() : shapeOrFn;
|
|
6386
|
+
}
|
|
6387
|
+
function unwrapZod(schema) {
|
|
6388
|
+
let current = schema;
|
|
6389
|
+
let optional = typeof current?.isOptional === "function" ? current.isOptional() : false;
|
|
6390
|
+
let description = current?._def?.description;
|
|
6391
|
+
let defaultValue;
|
|
6392
|
+
for (let i = 0; i < 8; i++) {
|
|
6393
|
+
const def = current?._def;
|
|
6394
|
+
const typeName = def?.typeName || "unknown";
|
|
6395
|
+
if (def?.description) description = def.description;
|
|
6396
|
+
if (!def) return { schema: current, typeName, optional, description, defaultValue };
|
|
6397
|
+
if (typeName === "ZodDefault") {
|
|
6398
|
+
optional = true;
|
|
6399
|
+
defaultValue = typeof def.defaultValue === "function" ? def.defaultValue() : def.defaultValue;
|
|
6400
|
+
current = def.innerType || def.type;
|
|
6401
|
+
continue;
|
|
6402
|
+
}
|
|
6403
|
+
if (typeName === "ZodOptional" || typeName === "ZodNullable") {
|
|
6404
|
+
optional = true;
|
|
6405
|
+
current = def.innerType || def.type;
|
|
6406
|
+
continue;
|
|
6407
|
+
}
|
|
6408
|
+
return { schema: current, typeName, optional, description, defaultValue };
|
|
6409
|
+
}
|
|
6410
|
+
return { schema: current, typeName: current?._def?.typeName || "unknown", optional, description, defaultValue };
|
|
6411
|
+
}
|
|
6412
|
+
function zodTypeToContractType(typeName) {
|
|
6413
|
+
switch (typeName) {
|
|
6414
|
+
case "ZodString":
|
|
6415
|
+
return "string";
|
|
6416
|
+
case "ZodNumber":
|
|
6417
|
+
return "number";
|
|
6418
|
+
case "ZodBoolean":
|
|
6419
|
+
return "boolean";
|
|
6420
|
+
case "ZodEnum":
|
|
6421
|
+
case "ZodNativeEnum":
|
|
6422
|
+
return "enum";
|
|
6423
|
+
case "ZodArray":
|
|
6424
|
+
return "array";
|
|
6425
|
+
case "ZodObject":
|
|
6426
|
+
return "object";
|
|
6427
|
+
default:
|
|
6428
|
+
return typeName.replace(/^Zod/, "").toLowerCase() || "unknown";
|
|
6429
|
+
}
|
|
6430
|
+
}
|
|
6431
|
+
function widgetFor(type, enumValues, schema) {
|
|
6432
|
+
if (enumValues) return "select";
|
|
6433
|
+
if (type === "boolean") return "checkbox";
|
|
6434
|
+
if (type === "number") return "number";
|
|
6435
|
+
if (type === "array") return "multi-select";
|
|
6436
|
+
if (type === "object") return "json";
|
|
6437
|
+
const checks = schema?._def?.checks;
|
|
6438
|
+
if (checks?.some((check) => check.kind === "url")) return "url";
|
|
6439
|
+
return "text";
|
|
6440
|
+
}
|
|
6441
|
+
function extractEnumValues(schema) {
|
|
6442
|
+
const def = schema?._def;
|
|
6443
|
+
const values = def?.values;
|
|
6444
|
+
if (Array.isArray(values)) return values.map(String);
|
|
6445
|
+
return void 0;
|
|
6446
|
+
}
|
|
6447
|
+
function summarizeZod(schema) {
|
|
6448
|
+
const unwrapped = unwrapZod(schema);
|
|
6449
|
+
if (unwrapped.typeName === "ZodArray") {
|
|
6450
|
+
const def = unwrapped.schema?._def;
|
|
6451
|
+
return {
|
|
6452
|
+
type: "array",
|
|
6453
|
+
items: summarizeZod(def?.type || def?.innerType)
|
|
6454
|
+
};
|
|
6455
|
+
}
|
|
6456
|
+
const shape = getShape(schema);
|
|
6457
|
+
if (!shape) {
|
|
6458
|
+
return {
|
|
6459
|
+
type: zodTypeToContractType(unwrapped.typeName),
|
|
6460
|
+
required: !unwrapped.optional,
|
|
6461
|
+
...unwrapped.description ? { description: unwrapped.description } : {}
|
|
6462
|
+
};
|
|
6463
|
+
}
|
|
6464
|
+
return Object.fromEntries(
|
|
6465
|
+
Object.entries(shape).map(([name, field]) => {
|
|
6466
|
+
const unwrapped2 = unwrapZod(field);
|
|
6467
|
+
return [name, {
|
|
6468
|
+
type: zodTypeToContractType(unwrapped2.typeName),
|
|
6469
|
+
required: !unwrapped2.optional,
|
|
6470
|
+
...unwrapped2.description ? { description: unwrapped2.description } : {}
|
|
6471
|
+
}];
|
|
6472
|
+
})
|
|
6473
|
+
);
|
|
6474
|
+
}
|
|
6475
|
+
function toLabel(name) {
|
|
6476
|
+
return name.replace(/([A-Z])/g, " $1").replace(/[-_]+/g, " ").replace(/\s+/g, " ").trim().replace(/^./, (char) => char.toUpperCase());
|
|
6477
|
+
}
|
|
6478
|
+
|
|
5675
6479
|
// src/plugin/loader.ts
|
|
5676
6480
|
var DEFAULT_PLUGIN_DIRS = [".xcli/plugins", "../.xcli/plugins"];
|
|
5677
6481
|
var XBrowserPluginLoader = class {
|
|
@@ -5714,6 +6518,13 @@ var XBrowserPluginLoader = class {
|
|
|
5714
6518
|
getLoadedPlugins() {
|
|
5715
6519
|
return this.loader.getLoadedPlugins();
|
|
5716
6520
|
}
|
|
6521
|
+
getPluginContract(siteName, commandName) {
|
|
6522
|
+
const site = this.core.loader.getSite(siteName);
|
|
6523
|
+
if (!site) return void 0;
|
|
6524
|
+
const contract = buildPluginContract(site);
|
|
6525
|
+
if (!commandName) return contract;
|
|
6526
|
+
return contract.commands.find((command) => command.name === commandName);
|
|
6527
|
+
}
|
|
5717
6528
|
async loadPlugin(pluginPath, id) {
|
|
5718
6529
|
return this.loader.loadPlugin(pluginPath, id);
|
|
5719
6530
|
}
|
|
@@ -6198,7 +7009,7 @@ function getTipsManager() {
|
|
|
6198
7009
|
|
|
6199
7010
|
// src/hooks/loader.ts
|
|
6200
7011
|
var builtinHooks = {
|
|
6201
|
-
screenshot: () => import("./screenshot-
|
|
7012
|
+
screenshot: () => import("./screenshot-CWAWMXVA.js").then((m) => m.screenshotHook)
|
|
6202
7013
|
};
|
|
6203
7014
|
async function loadHooks() {
|
|
6204
7015
|
const names = process.env.XBROWSER_HOOKS;
|
|
@@ -6217,45 +7028,13 @@ async function loadHooks() {
|
|
|
6217
7028
|
// src/executor.ts
|
|
6218
7029
|
import { homedir as homedir3 } from "os";
|
|
6219
7030
|
import { join as join3 } from "path";
|
|
6220
|
-
import { existsSync as existsSync6, readFileSync as readFileSync10, writeFileSync as writeFileSync5, mkdirSync as mkdirSync3 } from "fs";
|
|
6221
7031
|
var NAVIGATION_COMMANDS = /* @__PURE__ */ new Set(["goto", "back", "forward", "refresh"]);
|
|
6222
7032
|
var snapshotHintShown = /* @__PURE__ */ new WeakSet();
|
|
6223
7033
|
var STORAGE_DIR = join3(homedir3(), ".xbrowser", "storage");
|
|
6224
7034
|
var storageCache = /* @__PURE__ */ new Map();
|
|
6225
7035
|
function getPluginStorage(pluginName) {
|
|
6226
7036
|
if (!storageCache.has(pluginName)) {
|
|
6227
|
-
|
|
6228
|
-
let data = {};
|
|
6229
|
-
const load3 = () => {
|
|
6230
|
-
if (existsSync6(filePath)) {
|
|
6231
|
-
try {
|
|
6232
|
-
data = JSON.parse(readFileSync10(filePath, "utf-8"));
|
|
6233
|
-
} catch {
|
|
6234
|
-
data = {};
|
|
6235
|
-
}
|
|
6236
|
-
}
|
|
6237
|
-
};
|
|
6238
|
-
const save = () => {
|
|
6239
|
-
mkdirSync3(STORAGE_DIR, { recursive: true });
|
|
6240
|
-
writeFileSync5(filePath, JSON.stringify(data, null, 2), "utf-8");
|
|
6241
|
-
};
|
|
6242
|
-
load3();
|
|
6243
|
-
storageCache.set(pluginName, {
|
|
6244
|
-
get: async (key) => data[key] ?? null,
|
|
6245
|
-
set: async (key, value) => {
|
|
6246
|
-
data[key] = value;
|
|
6247
|
-
save();
|
|
6248
|
-
},
|
|
6249
|
-
delete: async (key) => {
|
|
6250
|
-
delete data[key];
|
|
6251
|
-
save();
|
|
6252
|
-
},
|
|
6253
|
-
clear: async () => {
|
|
6254
|
-
data = {};
|
|
6255
|
-
save();
|
|
6256
|
-
},
|
|
6257
|
-
keys: async () => Object.keys(data)
|
|
6258
|
-
});
|
|
7037
|
+
storageCache.set(pluginName, new PluginStorage(pluginName, STORAGE_DIR));
|
|
6259
7038
|
}
|
|
6260
7039
|
return storageCache.get(pluginName);
|
|
6261
7040
|
}
|
|
@@ -6316,7 +7095,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
6316
7095
|
}
|
|
6317
7096
|
let targetPageOverride = null;
|
|
6318
7097
|
if (_target && extraOpts?.cdpEndpoint) {
|
|
6319
|
-
const { findTargetPage } = await import("./browser-
|
|
7098
|
+
const { findTargetPage } = await import("./browser-GURRY444.js");
|
|
6320
7099
|
targetPageOverride = await findTargetPage(extraOpts.cdpEndpoint, _target);
|
|
6321
7100
|
if (!targetPageOverride) {
|
|
6322
7101
|
return errorResult(`Target "${_target}" not found. Use 'xbrowser targets --cdp ${extraOpts.cdpEndpoint}' to list available pages.`);
|
|
@@ -6333,7 +7112,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
6333
7112
|
params = result.data;
|
|
6334
7113
|
}
|
|
6335
7114
|
if (command.scope !== "cli" && !process.env.XBROWSER_DAEMON_WORKER) {
|
|
6336
|
-
const { forwardExec } = await import("./daemon-client-
|
|
7115
|
+
const { forwardExec } = await import("./daemon-client-3VM7VU7O.js");
|
|
6337
7116
|
const result = await forwardExec(commandName, params, sessionName, extraOpts?.cdpEndpoint);
|
|
6338
7117
|
if (result) return result;
|
|
6339
7118
|
}
|
|
@@ -6341,7 +7120,18 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
6341
7120
|
const existing = await findOrRestoreSession(sessionName, extraOpts?.cdpEndpoint);
|
|
6342
7121
|
if (existing) {
|
|
6343
7122
|
session = existing;
|
|
6344
|
-
if (
|
|
7123
|
+
if (session.page) {
|
|
7124
|
+
try {
|
|
7125
|
+
await Promise.race([
|
|
7126
|
+
session.page.evaluate(() => true),
|
|
7127
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error("timeout")), 3e3))
|
|
7128
|
+
]);
|
|
7129
|
+
} catch {
|
|
7130
|
+
await closeSessionByName(session.name);
|
|
7131
|
+
session = void 0;
|
|
7132
|
+
}
|
|
7133
|
+
}
|
|
7134
|
+
if (session && targetPageOverride && session.page) {
|
|
6345
7135
|
const currentUrl = session.page.url();
|
|
6346
7136
|
if (currentUrl !== targetPageOverride.url) {
|
|
6347
7137
|
await session.page.goto(targetPageOverride.url, { waitUntil: "domcontentloaded", timeout: 15e3 }).catch(() => {
|
|
@@ -6352,6 +7142,10 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
6352
7142
|
session = await createSession(sessionName, params.url, {
|
|
6353
7143
|
cdpEndpoint: extraOpts?.cdpEndpoint
|
|
6354
7144
|
});
|
|
7145
|
+
} else if (command.scope === "browser") {
|
|
7146
|
+
session = await createSession(sessionName, void 0, {
|
|
7147
|
+
cdpEndpoint: extraOpts?.cdpEndpoint
|
|
7148
|
+
});
|
|
6355
7149
|
} else if (command.scope !== "project") {
|
|
6356
7150
|
return errorResult(
|
|
6357
7151
|
`Session '${sessionName}' not found. Run "xbrowser session open <url>" first.`
|
|
@@ -6362,7 +7156,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
6362
7156
|
browser: session?.context.browser(),
|
|
6363
7157
|
browserContext: session?.context,
|
|
6364
7158
|
sessionId: session?.id,
|
|
6365
|
-
cdpEndpoint:
|
|
7159
|
+
cdpEndpoint: session?.cdpEndpoint || extraOpts?.cdpEndpoint,
|
|
6366
7160
|
args: [],
|
|
6367
7161
|
options: {},
|
|
6368
7162
|
cwd: process.cwd(),
|
|
@@ -6397,7 +7191,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
6397
7191
|
let refTips = [];
|
|
6398
7192
|
if (session?.page && command.selectorParams && command.selectorParams.length > 0) {
|
|
6399
7193
|
const cache = /* @__PURE__ */ new Map();
|
|
6400
|
-
const resolved = await resolveRefParams(session.page, params, command.selectorParams, cache);
|
|
7194
|
+
const resolved = await resolveRefParams(session.page, params, command.selectorParams, cache, session.id);
|
|
6401
7195
|
if (resolved.tips.length > 0) {
|
|
6402
7196
|
refTips = resolved.tips;
|
|
6403
7197
|
params = resolved.params;
|
|
@@ -6467,7 +7261,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
6467
7261
|
timestamp: start
|
|
6468
7262
|
});
|
|
6469
7263
|
if (isSuccess) {
|
|
6470
|
-
return { ...
|
|
7264
|
+
return { ...ok26(raw.data, merged.length > 0 ? merged : raw.tips), duration, ...hookOutputs ? { hookOutputs } : {} };
|
|
6471
7265
|
}
|
|
6472
7266
|
return { success: false, data: raw.data, message: raw.message, tips: merged.length > 0 ? merged : raw.tips || [], duration, ...hookOutputs ? { hookOutputs } : {} };
|
|
6473
7267
|
}
|
|
@@ -6480,7 +7274,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
6480
7274
|
duration,
|
|
6481
7275
|
timestamp: start
|
|
6482
7276
|
});
|
|
6483
|
-
return { ...
|
|
7277
|
+
return { ...ok26(raw, smartTips), duration, ...hookOutputs ? { hookOutputs } : {} };
|
|
6484
7278
|
} catch (err) {
|
|
6485
7279
|
const end = Date.now();
|
|
6486
7280
|
const duration = end - start;
|
|
@@ -6628,7 +7422,7 @@ async function executeChain(input, options) {
|
|
|
6628
7422
|
results.push({
|
|
6629
7423
|
command: `${cmdName} ${subCommand}`,
|
|
6630
7424
|
raw: cmdStr,
|
|
6631
|
-
...
|
|
7425
|
+
...ok26(data),
|
|
6632
7426
|
duration: duration2,
|
|
6633
7427
|
...hookOutputs ? { hookOutputs } : {}
|
|
6634
7428
|
});
|
|
@@ -6960,7 +7754,7 @@ async function replayEntry(entry, options = {}) {
|
|
|
6960
7754
|
}
|
|
6961
7755
|
|
|
6962
7756
|
// src/daemon/feedback-store.ts
|
|
6963
|
-
import { readFileSync as
|
|
7757
|
+
import { readFileSync as readFileSync10, writeFileSync as writeFileSync5, mkdirSync as mkdirSync3 } from "fs";
|
|
6964
7758
|
import { join as join4 } from "path";
|
|
6965
7759
|
import { homedir as homedir4 } from "os";
|
|
6966
7760
|
var FEEDBACK_FILE = join4(homedir4(), ".xbrowser", "feedback.json");
|
|
@@ -6971,7 +7765,7 @@ var FeedbackStore = class {
|
|
|
6971
7765
|
}
|
|
6972
7766
|
load() {
|
|
6973
7767
|
try {
|
|
6974
|
-
const data =
|
|
7768
|
+
const data = readFileSync10(FEEDBACK_FILE, "utf8");
|
|
6975
7769
|
this.entries = JSON.parse(data);
|
|
6976
7770
|
} catch {
|
|
6977
7771
|
this.entries = [];
|
|
@@ -6979,8 +7773,8 @@ var FeedbackStore = class {
|
|
|
6979
7773
|
}
|
|
6980
7774
|
save() {
|
|
6981
7775
|
try {
|
|
6982
|
-
|
|
6983
|
-
|
|
7776
|
+
mkdirSync3(join4(homedir4(), ".xbrowser"), { recursive: true });
|
|
7777
|
+
writeFileSync5(FEEDBACK_FILE, JSON.stringify(this.entries, null, 2));
|
|
6984
7778
|
} catch {
|
|
6985
7779
|
}
|
|
6986
7780
|
}
|
|
@@ -7361,6 +8155,12 @@ function createRPCHandler() {
|
|
|
7361
8155
|
return handleExec(params);
|
|
7362
8156
|
case "chain":
|
|
7363
8157
|
return handleChain(params);
|
|
8158
|
+
case "agent:observe":
|
|
8159
|
+
return handleAgentObserve(params);
|
|
8160
|
+
case "agent:act":
|
|
8161
|
+
return handleAgentAct(params);
|
|
8162
|
+
case "agent:wait":
|
|
8163
|
+
return handleAgentWait(params);
|
|
7364
8164
|
// ── Utility ──
|
|
7365
8165
|
case "ping":
|
|
7366
8166
|
return { ok: true, pid: process.pid };
|
|
@@ -7492,7 +8292,11 @@ function createRPCHandler() {
|
|
|
7492
8292
|
const existingSession = findSession(sessionName);
|
|
7493
8293
|
let endpoint;
|
|
7494
8294
|
if (cdp) {
|
|
7495
|
-
|
|
8295
|
+
try {
|
|
8296
|
+
endpoint = await resolveCDPEndpoint(cdp);
|
|
8297
|
+
} catch {
|
|
8298
|
+
endpoint = cdp;
|
|
8299
|
+
}
|
|
7496
8300
|
} else if (existingSession?.cdpEndpoint) {
|
|
7497
8301
|
endpoint = existingSession.cdpEndpoint;
|
|
7498
8302
|
} else {
|
|
@@ -7527,6 +8331,45 @@ function createRPCHandler() {
|
|
|
7527
8331
|
registerSessionIfNew(sessionName);
|
|
7528
8332
|
return result;
|
|
7529
8333
|
}
|
|
8334
|
+
async function handleAgentObserve(params) {
|
|
8335
|
+
const sessionName = params.session || "default";
|
|
8336
|
+
const commandParams = {
|
|
8337
|
+
includeHidden: !!params.includeHidden,
|
|
8338
|
+
...typeof params.limit === "number" ? { limit: params.limit } : {}
|
|
8339
|
+
};
|
|
8340
|
+
const result = await executeCommand("observe", commandParams, sessionName, {
|
|
8341
|
+
cdpEndpoint: params.cdpEndpoint
|
|
8342
|
+
});
|
|
8343
|
+
registerSessionIfNew(sessionName);
|
|
8344
|
+
return result;
|
|
8345
|
+
}
|
|
8346
|
+
async function handleAgentAct(params) {
|
|
8347
|
+
const sessionName = params.session || "default";
|
|
8348
|
+
const commandParams = {
|
|
8349
|
+
action: params.action || "click",
|
|
8350
|
+
force: !!params.force
|
|
8351
|
+
};
|
|
8352
|
+
for (const key of ["ref", "selector", "value", "key", "timeout"]) {
|
|
8353
|
+
if (params[key] !== void 0) commandParams[key] = params[key];
|
|
8354
|
+
}
|
|
8355
|
+
const result = await executeCommand("act", commandParams, sessionName, {
|
|
8356
|
+
cdpEndpoint: params.cdpEndpoint
|
|
8357
|
+
});
|
|
8358
|
+
registerSessionIfNew(sessionName);
|
|
8359
|
+
return result;
|
|
8360
|
+
}
|
|
8361
|
+
async function handleAgentWait(params) {
|
|
8362
|
+
const sessionName = params.session || "default";
|
|
8363
|
+
const commandParams = {};
|
|
8364
|
+
for (const key of ["selector", "state", "text", "url", "load", "fn", "screenHashChanged", "timeout", "pollInterval"]) {
|
|
8365
|
+
if (params[key] !== void 0) commandParams[key] = params[key];
|
|
8366
|
+
}
|
|
8367
|
+
const result = await executeCommand("waitFor", commandParams, sessionName, {
|
|
8368
|
+
cdpEndpoint: params.cdpEndpoint
|
|
8369
|
+
});
|
|
8370
|
+
registerSessionIfNew(sessionName);
|
|
8371
|
+
return result;
|
|
8372
|
+
}
|
|
7530
8373
|
function handleNetworkList(params) {
|
|
7531
8374
|
const sessionName = params.session || "default";
|
|
7532
8375
|
const opts = {};
|
|
@@ -7651,9 +8494,9 @@ function createRPCHandler() {
|
|
|
7651
8494
|
try {
|
|
7652
8495
|
const events = await sess.page.evaluate(() => window.__xb_evts || []);
|
|
7653
8496
|
const recordingsDir = join5(CONFIG_DIR, "recordings");
|
|
7654
|
-
|
|
8497
|
+
mkdirSync4(recordingsDir, { recursive: true });
|
|
7655
8498
|
const outPath = params.path || join5(recordingsDir, `recording-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.json`);
|
|
7656
|
-
|
|
8499
|
+
writeFileSync6(outPath, JSON.stringify({
|
|
7657
8500
|
startUrl: sess.page.url(),
|
|
7658
8501
|
recordedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7659
8502
|
events
|
|
@@ -8441,7 +9284,7 @@ var WSServer = class extends EventEmitter {
|
|
|
8441
9284
|
const vp = this.lastFrameViewport || initSc.page.viewportSize() || await initSc.page.evaluate(() => ({ width: window.innerWidth, height: window.innerHeight }));
|
|
8442
9285
|
this.sendToClient(clientId, {
|
|
8443
9286
|
type: "status",
|
|
8444
|
-
data: { status: "connected", sessionId: client.sessionId || void 0, viewport: vp }
|
|
9287
|
+
data: { status: "connected", sessionId: client.sessionId || void 0, viewport: vp ?? void 0 }
|
|
8445
9288
|
});
|
|
8446
9289
|
} catch {
|
|
8447
9290
|
}
|
|
@@ -8706,10 +9549,10 @@ var WSServer = class extends EventEmitter {
|
|
|
8706
9549
|
}
|
|
8707
9550
|
case "file_download": {
|
|
8708
9551
|
try {
|
|
8709
|
-
const { readFileSync:
|
|
9552
|
+
const { readFileSync: readFileSync12 } = await import("fs");
|
|
8710
9553
|
const { resolve: resolve9, basename } = await import("path");
|
|
8711
9554
|
const targetPath = resolve9(msg.path);
|
|
8712
|
-
const data =
|
|
9555
|
+
const data = readFileSync12(targetPath);
|
|
8713
9556
|
const base64 = data.toString("base64");
|
|
8714
9557
|
const ext = targetPath.split(".").pop()?.toLowerCase() || "";
|
|
8715
9558
|
const mimeMap = {
|
|
@@ -8859,7 +9702,7 @@ var WSServer = class extends EventEmitter {
|
|
|
8859
9702
|
const vp = this.lastFrameViewport || sc.page.viewportSize() || await sc.page.evaluate(() => ({ width: window.innerWidth, height: window.innerHeight }));
|
|
8860
9703
|
this.sendToClient(clientId, {
|
|
8861
9704
|
type: "status",
|
|
8862
|
-
data: { status: "connected", sessionId, viewport: vp }
|
|
9705
|
+
data: { status: "connected", sessionId, viewport: vp ?? void 0 }
|
|
8863
9706
|
});
|
|
8864
9707
|
} catch {
|
|
8865
9708
|
}
|
|
@@ -10008,8 +10851,8 @@ async function main() {
|
|
|
10008
10851
|
rpcHandler.setPreviewWS(previewWS);
|
|
10009
10852
|
previewWS.on("screencast-started", (sid) => log(`Preview screencast started: ${sid}`));
|
|
10010
10853
|
previewWS.on("screencast-stopped", (sid) => log(`Preview screencast stopped: ${sid}`));
|
|
10011
|
-
|
|
10012
|
-
|
|
10854
|
+
mkdirSync5(CONFIG_DIR2, { recursive: true });
|
|
10855
|
+
writeFileSync7(join6(CONFIG_DIR2, "daemon.json"), JSON.stringify({
|
|
10013
10856
|
port: daemonPort,
|
|
10014
10857
|
pid: process.pid,
|
|
10015
10858
|
startedAt: Date.now()
|