@xbrowser/cli 1.10.0 → 1.12.0

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