@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.
Files changed (30) hide show
  1. package/dist/{browser-R7B255ML.js → browser-53KUFEEM.js} +5 -1
  2. package/dist/{browser-I2HJZ7IP.js → browser-DSVV4GHS.js} +2 -2
  3. package/dist/{browser-GWBH6OJK.js → browser-GURRY444.js} +3 -1
  4. package/dist/cdp-driver-MNPR3HZH.js +2537 -0
  5. package/dist/cdp-driver-SSXUGXP6.js +47 -0
  6. package/dist/{chunk-2ONMTDLK.js → chunk-2BQZIT3S.js} +2535 -50
  7. package/dist/{chunk-KDYXFLAC.js → chunk-2MFXKN32.js} +2 -2
  8. package/dist/chunk-42RPMJ76.js +2530 -0
  9. package/dist/{chunk-F3ZWFCJJ.js → chunk-E4O5ZU3H.js} +2535 -50
  10. package/dist/{chunk-ATFTAKMN.js → chunk-IDVD44ED.js} +20 -0
  11. package/dist/chunk-T4J4C2NZ.js +250 -0
  12. package/dist/{chunk-RS6YYWTK.js → chunk-YKOHDEFV.js} +73 -38
  13. package/dist/cli.js +1176 -281
  14. package/dist/{convert-4DUWZIKH.js → convert-EGFYNICZ.js} +2 -0
  15. package/dist/{daemon-client-GX2UYIW4.js → daemon-client-3VM7VU7O.js} +22 -0
  16. package/dist/{daemon-client-3IJD6X4B.js → daemon-client-YAVQ343A.js} +7 -1
  17. package/dist/daemon-main.js +1004 -161
  18. package/dist/{extract-EGRXZSSK.js → extract-L2IW3IUB.js} +2 -0
  19. package/dist/{filter-OLAE26HN.js → filter-HC4RA7JY.js} +2 -0
  20. package/dist/index.d.ts +581 -41
  21. package/dist/index.js +1220 -321
  22. package/dist/launcher-KA7J32K5.js +19 -0
  23. package/dist/{network-store-YAF5OIBH.js → network-store-66A2RATI.js} +1 -0
  24. package/dist/{session-recorder-XET3DNML.js → session-recorder-MA75PKTQ.js} +1 -1
  25. package/package.json +3 -4
  26. package/dist/daemon-client-XWSSQBEA.js +0 -58
  27. package/dist/network-store-2S5HATEV.js +0 -194
  28. package/dist/parse-action-dsl-DRSPBALP.js +0 -72
  29. package/dist/screenshot-MB6R7RSS.js +0 -26
  30. 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-KDYXFLAC.js";
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-RS6YYWTK.js";
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 ok25,
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, { delay: p.delay, timeout: 1e4 });
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, { force: true, timeout: 1e4 });
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, { force: true, timeout: 1e4 });
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, { modifiers: p.modifiers, force: true, timeout: 1e4 });
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", { timeout: Math.min(p.timeout, 8e3) }).catch(() => {
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", { timeout }).catch(() => {
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", { timeout: p.timeout }).catch(() => {
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 = /^(e\d+)$/;
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 ref = val;
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, depth) {
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({ depth });
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({ timeout: 5e3 }).catch(() => "");
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/tab.ts
5186
+ // src/commands/agent.ts
4739
5187
  import { z as z22 } from "zod";
4740
- import { ok as ok21, fail as fail5 } from "@dyyz1993/xcli-core";
4741
- var TabParams = z22.object({
4742
- subcommand: z22.enum(["list", "new", "close", "switch"]),
4743
- url: z22.string().optional(),
4744
- index: z22.number().int().min(0).optional()
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: z22.object({
4752
- success: z22.boolean(),
4753
- data: z22.unknown()
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 ok21({ tabs, total: tabs.length, activeIndex: currentIndex });
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 ok21({
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 ok21({
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 ok21({
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 z23 } from "zod";
4875
- import { ok as ok22 } from "@dyyz1993/xcli-core";
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 = z23.object({
5116
- script: z23.string().optional(),
5117
- file: z23.string().optional(),
5118
- stdin: z23.boolean().optional(),
5119
- name: z23.string().optional(),
5120
- list: z23.boolean().optional(),
5121
- remove: z23.string().optional(),
5122
- base64: z23.string().optional()
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 ok22({ scripts });
5697
+ return ok23({ scripts });
5164
5698
  }
5165
5699
  if (params.remove) {
5166
5700
  const existed = registeredScripts.delete(params.remove);
5167
- return ok22({ removed: params.remove, existed });
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 ok22({ error: "No script content provided. Use --script, --file, --stdin, or --base64" });
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 ok22({
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 ok22({ registered: scriptName, executedImmediately: true });
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 z24 } from "zod";
5191
- import { ok as ok23, fail as fail6 } from "@dyyz1993/xcli-core";
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: z24.object({
5197
- strategy: z24.enum(["text", "role", "label", "placeholder", "testid"]),
5198
- value: z24.string(),
5199
- name: z24.string().optional(),
5200
- exact: z24.boolean().optional().default(false),
5201
- click: z24.boolean().optional().default(false),
5202
- fill: z24.string().optional(),
5203
- type: z24.string().optional(),
5204
- select: z24.string().optional(),
5205
- timeout: z24.number().optional().default(1e4)
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: z24.object({
5208
- matched: z24.number(),
5209
- selector: z24.string(),
5210
- action: z24.string().optional()
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 locator = buildLocator(page, p.strategy, p.value, {
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.first();
5769
+ const target = selectTarget(locator, p.strategy);
5224
5770
  if (count > 1) {
5225
- tips.push(`\u26A0\uFE0F Matched ${count} elements, using first`);
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(p.strategy, p.value, p.name);
5228
- let action;
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 = "click";
5232
- } else if (p.fill !== void 0) {
5233
- await target.fill(p.fill, { timeout: p.timeout, force: true });
5234
- action = `fill("${p.fill}")`;
5235
- } else if (p.type !== void 0) {
5236
- await target.type(p.type, { delay: 10, timeout: p.timeout });
5237
- action = `type("${p.type}")`;
5238
- } else if (p.select !== void 0) {
5239
- await target.selectOption(p.select, { timeout: p.timeout, force: true });
5240
- action = `select("${p.select}")`;
5241
- }
5242
- const result = ok23({ matched: count, selector, action });
5243
- if (tips.length > 0) result.tips = tips;
5244
- return result;
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", { timeout: 1e3 }).catch(() => "") || "";
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 z25 } from "zod";
5529
- import { ok as ok24 } from "@dyyz1993/xcli-core";
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 = z25.object({
5833
- platform: z25.enum(["devto", "medium", "csdn", "juejin", "quora"]).describe("Target platform for promotion"),
5834
- file: z25.string().describe("Path to Markdown file to publish"),
5835
- tags: z25.string().optional().describe("Comma-separated tags"),
5836
- title: z25.string().optional().describe("Custom title (default: extracted from file first heading)"),
5837
- search: z25.string().optional().describe("Quora: search query to find questions"),
5838
- cdpEndpoint: z25.string().optional().describe("CDP endpoint for agent-browser"),
5839
- session: z25.string().optional().describe("agent-browser session name")
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: z25.object({
5853
- success: z25.boolean(),
5854
- url: z25.string().optional(),
5855
- error: z25.string().optional(),
5856
- platform: z25.string()
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 ok24({
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 ok24(result);
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.9.2"
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
- const filePath = join3(STORAGE_DIR, `${pluginName}.json`);
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-I2HJZ7IP.js");
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-3IJD6X4B.js");
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 (targetPageOverride && session.page) {
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: extraOpts?.cdpEndpoint || session?.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 { ...ok25(raw.data, merged.length > 0 ? merged : raw.tips), duration, ...hookOutputs ? { hookOutputs } : {} };
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 { ...ok25(raw, smartTips), duration, ...hookOutputs ? { hookOutputs } : {} };
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
- ...ok25(data),
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 sessions = await listSessions();
7239
- if (sessions.length === 0) {
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 sessions) {
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 existsSync13,
8130
+ existsSync as existsSync12,
7337
8131
  readdirSync as readdirSync3,
7338
- mkdirSync as mkdirSync8,
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 existsSync8, cpSync as cpSync2, rmSync as rmSync2 } from "fs";
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 existsSync7,
8144
+ existsSync as existsSync6,
7351
8145
  readdirSync as readdirSync2,
7352
8146
  cpSync,
7353
8147
  rmSync,
7354
- mkdirSync as mkdirSync4,
7355
- readFileSync as readFileSync10,
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
- mkdirSync4(targetDir, { recursive: true });
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 (!existsSync7(indexPath)) {
8241
+ if (!existsSync6(indexPath)) {
7448
8242
  const indexJs = resolve9(dir, "index.js");
7449
- if (!existsSync7(indexJs)) {
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 (!existsSync7(pkgPath)) {
8248
+ if (!existsSync6(pkgPath)) {
7455
8249
  warnings.push("No package.json found");
7456
8250
  } else {
7457
8251
  try {
7458
- const pkg2 = JSON.parse(readFileSync10(pkgPath, "utf-8"));
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 (!existsSync8(srcPath)) {
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 (existsSync8(targetDir)) {
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 existsSync9, mkdirSync as mkdirSync5, readFileSync as readFileSync11, writeFileSync as writeFileSync5, rmSync as rmSync3, cpSync as cpSync3 } from "fs";
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
- mkdirSync5(tmpDir, { recursive: true });
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 (existsSync9(targetDir)) {
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 (existsSync9(pkgPath)) {
7551
- const pkg2 = JSON.parse(readFileSync11(pkgPath, "utf-8"));
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
- writeFileSync5(pkgPath, JSON.stringify(pkg2, null, 2));
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 existsSync10, readFileSync as readFileSync12, writeFileSync as writeFileSync6, rmSync as rmSync4, cpSync as cpSync4 } from "fs";
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 (existsSync10(targetDir)) {
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 (existsSync10(pkgPath)) {
7592
- const pkg2 = JSON.parse(readFileSync12(pkgPath, "utf-8"));
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
- writeFileSync6(pkgPath, JSON.stringify(pkg2, null, 2));
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 existsSync11, readFileSync as readFileSync13, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, rmSync as rmSync5, cpSync as cpSync5 } from "fs";
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
- mkdirSync6(tmpDir, { recursive: true });
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 (existsSync11(targetDir)) {
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 (existsSync11(pkgPath)) {
7637
- const pkg2 = JSON.parse(readFileSync13(pkgPath, "utf-8"));
8430
+ if (existsSync10(pkgPath)) {
8431
+ const pkg2 = JSON.parse(readFileSync12(pkgPath, "utf-8"));
7638
8432
  if (!pkg2._urlSource) {
7639
8433
  pkg2._urlSource = { url };
7640
- writeFileSync7(pkgPath, JSON.stringify(pkg2, null, 2));
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 existsSync12,
7659
- mkdirSync as mkdirSync7,
7660
- writeFileSync as writeFileSync8,
7661
- readFileSync as readFileSync14,
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 (existsSync12(targetDir) && !options?.force) {
8477
+ if (existsSync11(targetDir) && !options?.force) {
7684
8478
  throw new Error(`Plugin "${name}" already exists. Use --force to overwrite.`);
7685
8479
  }
7686
- mkdirSync7(targetDir, { recursive: true });
8480
+ mkdirSync6(targetDir, { recursive: true });
7687
8481
  const tmpDir = join7(tmpdir4(), `xbrowser-marketplace-${Date.now()}`);
7688
- mkdirSync7(tmpDir, { recursive: true });
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 (existsSync12(targetDir)) {
8512
+ if (existsSync11(targetDir)) {
7719
8513
  rmSync6(targetDir, { recursive: true, force: true });
7720
8514
  }
7721
- mkdirSync7(targetDir, { recursive: true });
8515
+ mkdirSync6(targetDir, { recursive: true });
7722
8516
  for (const file of manifest) {
7723
8517
  const filePath = resolve14(targetDir, file.path);
7724
- mkdirSync7(dirname2(filePath), { recursive: true });
7725
- writeFileSync8(filePath, Buffer.from(file.content, "base64"));
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 = readFileSync14(tarballPath);
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 (existsSync12(targetDir)) {
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
- writeFileSync8(tarballPath, buffer);
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 (existsSync12(targetDir)) {
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 (!existsSync12(pkgPath)) {
7815
- writeFileSync8(pkgPath, JSON.stringify(packageJson, null, 2));
8608
+ if (!existsSync11(pkgPath)) {
8609
+ writeFileSync7(pkgPath, JSON.stringify(packageJson, null, 2));
7816
8610
  } else {
7817
8611
  try {
7818
- const existing = JSON.parse(readFileSync14(pkgPath, "utf-8"));
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
- writeFileSync8(pkgPath, JSON.stringify(merged, null, 2));
8618
+ writeFileSync7(pkgPath, JSON.stringify(merged, null, 2));
7825
8619
  } catch {
7826
- writeFileSync8(pkgPath, JSON.stringify(packageJson, null, 2));
8620
+ writeFileSync7(pkgPath, JSON.stringify(packageJson, null, 2));
7827
8621
  }
7828
8622
  }
7829
8623
  }
7830
8624
  function ensureIndexFile(plugin, name, targetDir) {
7831
- if (!existsSync12(resolve14(targetDir, "index.ts")) && !existsSync12(resolve14(targetDir, "index.js"))) {
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
- writeFileSync8(
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 (existsSync13(targetDir) && !options?.force) {
8679
+ if (existsSync12(targetDir) && !options?.force) {
7886
8680
  throw new Error(`Plugin "${name}" already exists. Use --force to overwrite.`);
7887
8681
  }
7888
- mkdirSync8(targetDir, { recursive: true });
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 (!existsSync13(targetDir)) {
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 (!existsSync13(this.pluginsDir)) return [];
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 (!existsSync13(indexPath) && !existsSync13(indexJsPath)) continue;
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 (existsSync13(filePath)) return "url";
8786
+ if (existsSync12(filePath)) return "url";
7993
8787
  }
7994
8788
  if (source.endsWith(".git") || source.includes("github.com/")) return "git";
7995
- if (existsSync13(resolve15(source))) return "local";
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 sessions = await forwardSessionList();
9332
- outputResult({ sessions }, mode);
10146
+ const sessions2 = await forwardSessionList();
10147
+ outputResult({ sessions: sessions2 }, mode);
9333
10148
  } catch {
9334
- const sessions = await listSessions();
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 sessions = await forwardSessionList();
9356
- for (const s of sessions) {
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 buildRuntimeCommandsMap() {
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().map((c) => c.name);
9389
- if (cmds.length > 0) {
9390
- map.set(site.name, cmds);
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 runtimeCommands = await buildRuntimeCommandsMap();
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 dynamicCommands = runtimeCommands.get(p.name);
9600
- const commands = dynamicCommands || staticCommands;
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 readFileSync15 } from "fs";
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 = readFileSync15(filePath, "utf-8");
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 sessions = getAllSessions().map((s) => ({
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
- printChainResult(chainResult);
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-3IJD6X4B.js");
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, params, sessionName, cdpEndpoint, userTimeout);
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
- if (isCommandResult2(result)) {
11422
- if (mode === "json" || mode === "yaml") {
11423
- console.log(outputFormatter2.format(result.data, { mode, color: false, emoji: false }));
11424
- if (result.tips?.length) {
11425
- for (const tip of result.tips) console.error(`\u{1F4A1} ${tip}`);
11426
- }
11427
- } else {
11428
- console.log(outputFormatter2.format(result.data, { mode: "text", color: true, emoji: true }));
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
- } else if (result && typeof result === "object") {
11434
- const obj = result;
11435
- if (mode === "json" || mode === "yaml") {
11436
- console.log(outputFormatter2.format(obj.data ?? obj, { mode, color: false, emoji: false }));
11437
- const tips = obj.tips;
11438
- if (tips?.length) {
11439
- for (const tip of tips) console.error(`\u{1F4A1} ${tip}`);
11440
- }
11441
- } else {
11442
- if (obj.data) outputResult(obj.data, mode);
11443
- const tips = obj.tips;
11444
- if (tips?.length) {
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: readFileSync17 } = await import("fs");
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 = readFileSync17(targetPath);
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 { chromium } = await import("playwright");
13941
- const browser = await chromium.launch({
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
  };