@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/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  detectAntiBot,
3
3
  formatDetectionMessage
4
- } from "./chunk-INTQPBYF.js";
4
+ } from "./chunk-JKVUFP3G.js";
5
5
  import {
6
6
  forwardCommandLog,
7
7
  forwardNetworkAnalyze,
@@ -25,7 +25,7 @@ import {
25
25
  forwardSessionList,
26
26
  forwardViewerCheckSelector,
27
27
  version
28
- } from "./chunk-YMUSHPU4.js";
28
+ } from "./chunk-SH6OTPXN.js";
29
29
  import {
30
30
  buildViewerUrl
31
31
  } from "./chunk-3OD76SUE.js";
@@ -41,8 +41,9 @@ import {
41
41
  getMarketplaceUrl,
42
42
  loadConfig,
43
43
  resolveNpmPackageWithFallback,
44
+ resolveScreenshotsDir,
44
45
  setConfigValue
45
- } from "./chunk-DWDXEGVK.js";
46
+ } from "./chunk-NKW4A74J.js";
46
47
  import {
47
48
  getDaemonConfig,
48
49
  getDaemonProcessStatus,
@@ -66,7 +67,7 @@ import {
66
67
  } from "./chunk-ANVL2ID2.js";
67
68
  import {
68
69
  SessionRecorder
69
- } from "./chunk-4452SPFI.js";
70
+ } from "./chunk-NODRQGOK.js";
70
71
  import {
71
72
  addKnownIssue,
72
73
  getKnowledgePath,
@@ -91,15 +92,17 @@ import {
91
92
  resolveLaunchOpts,
92
93
  saveSessionDiskMeta,
93
94
  setActivePage
94
- } from "./chunk-WSCP7QCJ.js";
95
- import "./chunk-RRBXV7KE.js";
95
+ } from "./chunk-R6IJ6PIV.js";
96
+ import "./chunk-DKM2JOZL.js";
96
97
  import "./chunk-TNEN6VQ2.js";
97
98
  import {
98
99
  errMsg
99
100
  } from "./chunk-GDKLH7ZY.js";
100
101
  import {
101
102
  CDPInterceptorProxy,
102
- advise,
103
+ advise
104
+ } from "./chunk-A6LPGFAL.js";
105
+ import {
103
106
  automationSignalsRule,
104
107
  createRuleEngine,
105
108
  domMutationRule,
@@ -110,15 +113,15 @@ import {
110
113
  mouseTrajectoryRule,
111
114
  networkAnomalyRule,
112
115
  pageLifecycleRule
113
- } from "./chunk-ABXMBNQ6.js";
116
+ } from "./chunk-H2A5JUK5.js";
114
117
  import {
115
118
  __require
116
119
  } from "./chunk-KFQGP6VL.js";
117
120
 
118
121
  // src/executor.ts
119
122
  import {
120
- ok as ok25,
121
- fail as fail11,
123
+ ok as ok28,
124
+ fail as fail13,
122
125
  isCommandResult,
123
126
  CompositeStorage as CompositeStorage2,
124
127
  TipCollector as TipCollector2,
@@ -491,14 +494,23 @@ var CAPTCHA_SELECTORS = {
491
494
  generic: '#captcha, .captcha-container, [class*="captcha"]'
492
495
  };
493
496
  async function detectCaptcha(page) {
494
- for (const [type, selector] of Object.entries(CAPTCHA_SELECTORS)) {
495
- const el = await page.$(selector);
496
- if (el) {
497
- const iframeUrl = await el.getAttribute("src").catch(() => void 0);
498
- return { type, selector, iframeUrl: iframeUrl || void 0 };
499
- }
500
- }
501
- return null;
497
+ const hit = await page.evaluate(`
498
+ (function() {
499
+ var S = ${JSON.stringify(CAPTCHA_SELECTORS)};
500
+ for (var type in S) {
501
+ var sel = S[type];
502
+ var el;
503
+ try { el = document.querySelector(sel); } catch (e) { continue; }
504
+ if (el) {
505
+ var url = el.getAttribute && el.getAttribute('src');
506
+ return { type: type, selector: sel, iframeUrl: url || undefined };
507
+ }
508
+ }
509
+ return null;
510
+ })()
511
+ `).catch(() => null);
512
+ if (!hit) return null;
513
+ return { type: hit.type, selector: hit.selector, iframeUrl: hit.iframeUrl || void 0 };
502
514
  }
503
515
  async function waitForCaptchaSolved(page, captchaInfo, timeoutMs = 18e4) {
504
516
  const startTime = Date.now();
@@ -549,12 +561,24 @@ var clickCommand = registerCommand({
549
561
  let detectedNewPage;
550
562
  let cleanup;
551
563
  if (ctx.browserContext?.on) {
564
+ const targetIdOf = (p2) => p2._targetId;
565
+ let targetIdsBefore;
566
+ try {
567
+ targetIdsBefore = new Set(ctx.browserContext.pages().map(targetIdOf));
568
+ } catch {
569
+ targetIdsBefore = /* @__PURE__ */ new Set();
570
+ }
552
571
  const pagePromise = new Promise((resolve10) => {
553
572
  const timer = setTimeout(() => {
554
573
  ctx.browserContext.off("page", handler);
555
574
  resolve10(void 0);
556
575
  }, 3e3);
557
576
  const handler = (page2) => {
577
+ if (targetIdsBefore.has(targetIdOf(page2))) return;
578
+ try {
579
+ if ((page2.url?.() ?? "") === "about:blank") return;
580
+ } catch {
581
+ }
558
582
  clearTimeout(timer);
559
583
  ctx.browserContext.off("page", handler);
560
584
  resolve10(page2);
@@ -955,9 +979,11 @@ var mouseCommand = registerCommand({
955
979
  await ctx.page.mouse.move(p.x, p.y, { steps: p.steps || 1 });
956
980
  break;
957
981
  case "down":
982
+ await ctx.page.mouse.move(p.x, p.y);
958
983
  await ctx.page.mouse.down({ button });
959
984
  break;
960
985
  case "up":
986
+ await ctx.page.mouse.move(p.x, p.y);
961
987
  await ctx.page.mouse.up({ button });
962
988
  break;
963
989
  case "click":
@@ -971,28 +997,441 @@ var mouseCommand = registerCommand({
971
997
  }
972
998
  });
973
999
 
974
- // src/commands/evaluate.ts
1000
+ // src/commands/find-visual.ts
975
1001
  import { z as z7 } from "zod";
976
- import { ok as ok7, normalizeTips as normalizeTips2 } from "@dyyz1993/xcli-core";
1002
+ import { ok as ok7, fail as fail2 } from "@dyyz1993/xcli-core";
1003
+ import { readFileSync, existsSync } from "fs";
1004
+ import { homedir as homedir2 } from "os";
1005
+ import { join as join2 } from "path";
1006
+ function loadVLMCredentials() {
1007
+ const candidates = [
1008
+ process.env.XBROWSER_VLM_CONFIG,
1009
+ join2(homedir2(), ".zcode", "v2", "config.json")
1010
+ ].filter(Boolean);
1011
+ for (const file of candidates) {
1012
+ try {
1013
+ if (!existsSync(file)) continue;
1014
+ const cfg = JSON.parse(readFileSync(file, "utf8"));
1015
+ const provider = Object.values(cfg.provider ?? {})[0];
1016
+ if (provider?.options?.apiKey && provider.options.baseURL) {
1017
+ const models = Object.keys(provider.models ?? {});
1018
+ const vl = models.find((m) => /vl|vision/i.test(m));
1019
+ const flash = models.find((m) => /flash/i.test(m) && /5\.?3/i.test(m));
1020
+ return {
1021
+ apiKey: provider.options.apiKey,
1022
+ baseURL: provider.options.baseURL.replace(/\/$/, ""),
1023
+ model: process.env.XBROWSER_VLM_MODEL || vl || flash || models.find((m) => /5\.[23]/.test(m)) || models[0]
1024
+ };
1025
+ }
1026
+ } catch {
1027
+ }
1028
+ }
1029
+ return null;
1030
+ }
1031
+ async function locateByVLM(creds, imageBase64, target, feedback) {
1032
+ 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}"""
1033
+
1034
+ \u4E25\u683C\u53EA\u8F93\u51FA\u4E00\u4E2A JSON \u5BF9\u8C61\uFF08\u65E0 markdown\u3001\u65E0\u89E3\u91CA\uFF09\uFF1A
1035
+ {"label":"<\u5143\u7D20\u540D>","box":{"x":<\u5DE6\u4E0Ax>,"y":<\u5DE6\u4E0Ay>,"width":<\u5BBD>,"height":<\u9AD8>},"confidence":"high|medium|low"}
1036
+ \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
1037
+ ${feedback ? `\u6CE8\u610F\uFF1A${feedback}` : ""}`;
1038
+ const resp = await fetch(creds.baseURL + "/v1/messages", {
1039
+ method: "POST",
1040
+ headers: { "content-type": "application/json", "x-api-key": creds.apiKey, "anthropic-version": "2023-06-01" },
1041
+ body: JSON.stringify({
1042
+ model: creds.model,
1043
+ max_tokens: 2e3,
1044
+ // thinking 模型的思考块也计入配额,300 会在 text 前截断
1045
+ messages: [{
1046
+ role: "user",
1047
+ content: [
1048
+ { type: "image", source: { type: "base64", media_type: "image/png", data: imageBase64 } },
1049
+ { type: "text", text: prompt }
1050
+ ]
1051
+ }]
1052
+ }),
1053
+ signal: AbortSignal.timeout(6e4)
1054
+ });
1055
+ if (!resp.ok) throw new Error(`VLM HTTP ${resp.status}`);
1056
+ const data = await resp.json();
1057
+ const text = (data.content ?? []).filter((c) => c.type === "text").map((c) => c.text ?? "").join("");
1058
+ const m = text.match(/\{[\s\S]*\}/);
1059
+ if (!m) {
1060
+ console.error("[find-visual] VLM raw (no JSON):", text.substring(0, 200));
1061
+ return null;
1062
+ }
1063
+ const hit = JSON.parse(m[0]);
1064
+ if (!hit.label || !hit.box) return null;
1065
+ return hit;
1066
+ }
1067
+ var findVisualCommand = registerCommand({
1068
+ name: "find-visual",
1069
+ description: "Visual element location: screenshot \u2192 VLM \u2192 page coordinates (optionally click)",
1070
+ scope: "page",
1071
+ parameters: z7.object({
1072
+ 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'),
1073
+ click: z7.boolean().optional().describe("\u627E\u5230\u540E\u7ACB\u5373\u70B9\u51FB\uFF08\u771F\u5B9E\u9F20\u6807\u4E8B\u4EF6\uFF09"),
1074
+ threshold: z7.coerce.number().optional().describe("\u7F6E\u4FE1\u5EA6\u9608\u503C\uFF0C\u4F4E\u4E8E\u5219\u62A5 not-found\uFF08\u9ED8\u8BA4 0\uFF09"),
1075
+ 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")
1076
+ }),
1077
+ result: z7.object({
1078
+ found: z7.boolean(),
1079
+ element: z7.string(),
1080
+ box: z7.unknown().optional(),
1081
+ center: z7.unknown().optional(),
1082
+ confidence: z7.string().optional(),
1083
+ model: z7.string().optional(),
1084
+ clicked: z7.boolean().optional(),
1085
+ screenshot: z7.string().optional()
1086
+ }),
1087
+ handler: async (p, ctx) => {
1088
+ const page = ctx.page;
1089
+ const creds = loadVLMCredentials();
1090
+ if (!creds) {
1091
+ 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", [
1092
+ "\u4E5F\u53EF\u7528 XBROWSER_VLM_MODEL \u6307\u5B9A\u6A21\u578B\uFF08\u9ED8\u8BA4\u81EA\u52A8\u9009 VL/\u65D7\u8230\u6A21\u578B\uFF09"
1093
+ ]);
1094
+ }
1095
+ const buffer = await page.screenshot({ type: "png" });
1096
+ const imageBase64 = buffer.toString("base64");
1097
+ const imgW = buffer.readUInt32BE(16);
1098
+ const imgH = buffer.readUInt32BE(20);
1099
+ let hit = null;
1100
+ let feedback = "";
1101
+ try {
1102
+ for (let attempt = 0; attempt < 3 && !hit; attempt++) {
1103
+ const candidate = await locateByVLM(creds, imageBase64, p.element, feedback);
1104
+ if (!candidate) break;
1105
+ const b = candidate.box;
1106
+ const inBounds = b.x >= 0 && b.y >= 0 && b.x + b.width <= imgW + 2 && b.y + b.height <= imgH + 2;
1107
+ const sane = b.width > 2 && b.height > 2;
1108
+ if (inBounds && sane) {
1109
+ hit = candidate;
1110
+ break;
1111
+ }
1112
+ 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`;
1113
+ }
1114
+ } catch (e) {
1115
+ return fail2(`VLM \u8C03\u7528\u5931\u8D25: ${e instanceof Error ? e.message : String(e)}`);
1116
+ }
1117
+ if (!hit) {
1118
+ return ok7({ found: false, element: p.element, model: creds.model }, [
1119
+ `\u622A\u56FE\u4E2D\u672A\u627E\u5230: ${p.element}`
1120
+ ]);
1121
+ }
1122
+ if (p.zoom !== false && hit) {
1123
+ try {
1124
+ const pad = Math.max(20, Math.round(Math.max(hit.box.width, hit.box.height) * 0.5));
1125
+ const clip = {
1126
+ x: Math.max(0, hit.box.x - pad),
1127
+ y: Math.max(0, hit.box.y - pad),
1128
+ width: Math.min(imgW - Math.max(0, hit.box.x - pad), hit.box.width + pad * 2),
1129
+ height: Math.min(imgH - Math.max(0, hit.box.y - pad), hit.box.height + pad * 2)
1130
+ };
1131
+ if (clip.width > 8 && clip.height > 8) {
1132
+ const zoomBuf = await page.screenshot({ type: "png", clip });
1133
+ const zoomB64 = zoomBuf.toString("base64");
1134
+ const zoomW = zoomBuf.readUInt32BE(16);
1135
+ const zoomH = zoomBuf.readUInt32BE(20);
1136
+ const refined = await locateByVLM(creds, zoomB64, p.element);
1137
+ if (refined) {
1138
+ const rb = refined.box;
1139
+ 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;
1140
+ if (inZoom) {
1141
+ hit = {
1142
+ label: refined.label || hit.label,
1143
+ box: {
1144
+ x: clip.x + rb.x,
1145
+ y: clip.y + rb.y,
1146
+ width: rb.width,
1147
+ height: rb.height
1148
+ },
1149
+ confidence: refined.confidence === "low" ? hit.confidence : refined.confidence
1150
+ };
1151
+ }
1152
+ }
1153
+ }
1154
+ } catch {
1155
+ }
1156
+ }
1157
+ if (p.threshold !== void 0) {
1158
+ const rank = { high: 3, medium: 2, low: 1 };
1159
+ const thresholdName = p.threshold >= 3 ? "high" : p.threshold >= 2 ? "medium" : "low";
1160
+ if ((rank[hit.confidence ?? "low"] ?? 1) < (rank[thresholdName] ?? 1)) {
1161
+ return ok7({ found: false, element: p.element, confidence: hit.confidence, box: hit.box, model: creds.model }, [
1162
+ `\u7F6E\u4FE1\u5EA6 ${hit.confidence} \u4F4E\u4E8E\u9608\u503C ${p.threshold}`
1163
+ ]);
1164
+ }
1165
+ }
1166
+ const viewport = await page.evaluate("JSON.stringify({w: window.innerWidth, h: window.innerHeight})").then((v) => JSON.parse(String(v))).catch(() => ({ w: 1280, h: 800 }));
1167
+ const pngW = buffer.readUInt32BE(16);
1168
+ const pngH = buffer.readUInt32BE(20);
1169
+ const scaleX = viewport.w / pngW;
1170
+ const scaleY = viewport.h / pngH;
1171
+ const center = {
1172
+ x: Math.round((hit.box.x + hit.box.width / 2) * scaleX),
1173
+ y: Math.round((hit.box.y + hit.box.height / 2) * scaleY)
1174
+ };
1175
+ let clicked = false;
1176
+ if (p.click) {
1177
+ await page.mouse.click(center.x, center.y, { stealth: true });
1178
+ clicked = true;
1179
+ }
1180
+ return ok7({
1181
+ found: true,
1182
+ element: p.element,
1183
+ box: hit.box,
1184
+ center,
1185
+ confidence: hit.confidence,
1186
+ model: creds.model,
1187
+ clicked,
1188
+ screenshot: `data:image/png;base64,${imageBase64.substring(0, 64)}... (${Math.round(imageBase64.length / 1024)}KB)`
1189
+ }, [
1190
+ 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`
1191
+ ]);
1192
+ }
1193
+ });
1194
+
1195
+ // src/commands/vision-task.ts
1196
+ import { z as z8 } from "zod";
1197
+ import { ok as ok8, fail as fail3 } from "@dyyz1993/xcli-core";
1198
+ import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
1199
+ import { homedir as homedir3 } from "os";
1200
+ import { join as join3 } from "path";
1201
+ function loadVLMCredentials2() {
1202
+ const candidates = [
1203
+ process.env.XBROWSER_VLM_CONFIG,
1204
+ join3(homedir3(), ".zcode", "v2", "config.json")
1205
+ ].filter(Boolean);
1206
+ for (const file of candidates) {
1207
+ try {
1208
+ if (!existsSync2(file)) continue;
1209
+ const cfg = JSON.parse(readFileSync2(file, "utf8"));
1210
+ const provider = Object.values(cfg.provider ?? {})[0];
1211
+ if (provider?.options?.apiKey && provider.options.baseURL) {
1212
+ const models = Object.keys(provider.models ?? {});
1213
+ const vl = models.find((m) => /vl|vision/i.test(m));
1214
+ const flash = models.find((m) => /flash/i.test(m) && /5\.?3/i.test(m));
1215
+ return {
1216
+ apiKey: provider.options.apiKey,
1217
+ baseURL: provider.options.baseURL.replace(/\/$/, ""),
1218
+ model: process.env.XBROWSER_VLM_MODEL || vl || flash || models.find((m) => /5\.[23]/.test(m)) || models[0]
1219
+ };
1220
+ }
1221
+ } catch {
1222
+ }
1223
+ }
1224
+ return null;
1225
+ }
1226
+ async function vlmAsk(creds, content, maxTokens = 2e3) {
1227
+ const resp = await fetch(creds.baseURL + "/v1/messages", {
1228
+ method: "POST",
1229
+ headers: { "content-type": "application/json", "x-api-key": creds.apiKey, "anthropic-version": "2023-06-01" },
1230
+ body: JSON.stringify({ model: creds.model, max_tokens: maxTokens, messages: [{ role: "user", content }] }),
1231
+ signal: AbortSignal.timeout(9e4)
1232
+ });
1233
+ if (!resp.ok) throw new Error(`VLM HTTP ${resp.status}`);
1234
+ const data = await resp.json();
1235
+ return (data.content ?? []).filter((c) => c.type === "text").map((c) => c.text ?? "").join("");
1236
+ }
1237
+ async function vlmAskRetry(creds, content, maxTokens = 2e3) {
1238
+ let last = "";
1239
+ for (let i = 0; i < 2; i++) {
1240
+ last = await vlmAsk(creds, content, maxTokens);
1241
+ if (last.trim()) return last;
1242
+ await new Promise((r) => setTimeout(r, 1200));
1243
+ }
1244
+ return last;
1245
+ }
1246
+ function pngDims(b64) {
1247
+ const buf = Buffer.from(b64, "base64");
1248
+ return { w: buf.readUInt32BE(16), h: buf.readUInt32BE(20) };
1249
+ }
1250
+ function parseDecision(text) {
1251
+ const m = text.match(/\{[\s\S]*\}/);
1252
+ if (!m) return null;
1253
+ try {
1254
+ const j = JSON.parse(m[0]);
1255
+ if (!j.action || !j.action.type) return null;
1256
+ return j;
1257
+ } catch {
1258
+ return null;
1259
+ }
1260
+ }
1261
+ 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
1262
+
1263
+ \u4E25\u683C\u53EA\u8F93\u51FA\u4E00\u4E2A JSON\uFF08\u65E0 markdown\uFF09\uFF1A
1264
+ {"thought":"<\u7B80\u77ED\u63A8\u7406>","action":{"type":"click","x":<int>,"y":<int>}
1265
+ \u6216 {"type":"type","text":"<\u8981\u8F93\u5165\u7684\u6587\u5B57>"}\uFF08\u8F93\u5165\u524D\u987B\u5DF2\u70B9\u51FB\u805A\u7126\u8F93\u5165\u6846\uFF09
1266
+ \u6216 {"type":"key","key":"Enter"}\uFF08key \u540D\uFF1AEnter/Tab/Escape/ArrowDown\u2026\uFF09
1267
+ \u6216 {"type":"scroll","direction":"down","amount":<px>}
1268
+ \u6216 {"type":"done","summary":"<\u4EFB\u52A1\u5B8C\u6210\u6458\u8981>"}
1269
+ \u6216 {"type":"stuck","reason":"<\u65E0\u6CD5\u7EE7\u7EED\u7684\u539F\u56E0>"}}
1270
+
1271
+ \u5750\u6807\u4EE5\u622A\u56FE\u5DE6\u4E0A\u89D2\u4E3A\u539F\u70B9\u3001\u50CF\u7D20\u4E3A\u5355\u4F4D\u3002\u89C4\u5219\uFF1A
1272
+ 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
1273
+ 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
1274
+ 3. \u4EFB\u52A1\u76EE\u6807\u8FBE\u6210\u7ACB\u5373\u8F93\u51FA done\uFF1B5 \u6B65\u65E0\u8FDB\u5C55\u8F93\u51FA stuck`;
1275
+ var visionTaskCommand = registerCommand({
1276
+ name: "vision-task",
1277
+ description: "Vision-loop agent: screenshot \u2192 VLM decide \u2192 CDP act \u2192 verify, until done",
1278
+ scope: "page",
1279
+ parameters: z8.object({
1280
+ task: z8.string().describe('\u81EA\u7136\u8BED\u8A00\u4EFB\u52A1\uFF0C\u5982 "\u641C\u7D22 xbrowser \u5E76\u6253\u5F00\u7B2C\u4E00\u7BC7\u6587\u7AE0"'),
1281
+ maxSteps: z8.coerce.number().optional().describe("\u6700\u5927\u6B65\u6570\uFF08\u9ED8\u8BA4 12\uFF09"),
1282
+ settleMs: z8.coerce.number().optional().describe("\u6BCF\u6B65\u540E\u7B49\u5F85\u9875\u9762\u7A33\u5B9A\u7684\u6BEB\u79D2\u6570\uFF08\u9ED8\u8BA4 1500\uFF09")
1283
+ }),
1284
+ result: z8.object({
1285
+ done: z8.boolean(),
1286
+ steps: z8.unknown().optional(),
1287
+ summary: z8.string().optional(),
1288
+ model: z8.string().optional()
1289
+ }),
1290
+ handler: async (p, ctx) => {
1291
+ let page = ctx.page;
1292
+ const creds = loadVLMCredentials2();
1293
+ if (!creds) {
1294
+ return fail3("\u672A\u627E\u5230 VLM \u51ED\u636E\uFF1A\u9700\u8981 ~/.zcode/v2/config.json \u6216 XBROWSER_VLM_CONFIG");
1295
+ }
1296
+ const maxSteps = p.maxSteps ?? 12;
1297
+ const settleMs = p.settleMs ?? 1500;
1298
+ const steps = [];
1299
+ const history = [];
1300
+ let lastUrl = page.url();
1301
+ for (let step = 1; step <= maxSteps; step++) {
1302
+ if (page.url() === "about:blank") {
1303
+ try {
1304
+ const ctxObj = page.context?.();
1305
+ const pages = ctxObj?.pages?.() || [];
1306
+ let real;
1307
+ for (let i = pages.length - 1; i >= 0; i--) {
1308
+ const u = pages[i]?.url?.() ?? "";
1309
+ if (u && u !== "about:blank") {
1310
+ real = pages[i];
1311
+ break;
1312
+ }
1313
+ }
1314
+ if (real) {
1315
+ await real.bringToFront().catch(() => {
1316
+ });
1317
+ page = real;
1318
+ }
1319
+ } catch {
1320
+ }
1321
+ }
1322
+ const buffer = await page.screenshot({ type: "png" });
1323
+ const b64 = buffer.toString("base64");
1324
+ const { w, h } = pngDims(b64);
1325
+ const obsText = [
1326
+ `\u4EFB\u52A1\uFF1A${p.task}`,
1327
+ `\u622A\u56FE\u5C3A\u5BF8\uFF1A${w}x${h}\uFF08\u5750\u6807\u539F\u70B9\uFF1A\u5DE6\u4E0A\u89D2\uFF09`,
1328
+ `\u5F53\u524D URL\uFF1A${page.url()}`,
1329
+ history.length ? `\u5DF2\u6267\u884C\u52A8\u4F5C\uFF1A
1330
+ ${history.slice(-6).join("\n")}` : "\u5C1A\u65E0\u52A8\u4F5C\u3002"
1331
+ ].join("\n");
1332
+ const reply = await vlmAskRetry(creds, [
1333
+ { type: "image", source: { type: "base64", media_type: "image/png", data: b64 } },
1334
+ { type: "text", text: `${SYSTEM_PROMPT}
1335
+
1336
+ ${obsText}` }
1337
+ ]);
1338
+ const decision = parseDecision(reply);
1339
+ if (!decision) {
1340
+ steps.push({ n: step, thought: "(unparseable)", action: "stop", ts: Date.now() });
1341
+ return ok8({ done: false, steps, summary: `\u7B2C ${step} \u6B65 VLM \u8F93\u51FA\u65E0\u6CD5\u89E3\u6790: ${reply.substring(0, 120)}`, model: creds.model });
1342
+ }
1343
+ const a = decision.action;
1344
+ let acted = "";
1345
+ try {
1346
+ if (a.type === "done") {
1347
+ steps.push({ n: step, thought: decision.thought, action: "done", ts: Date.now() });
1348
+ return ok8({ done: true, steps, summary: a.summary, model: creds.model });
1349
+ }
1350
+ if (a.type === "stuck") {
1351
+ steps.push({ n: step, thought: decision.thought, action: "stuck", ts: Date.now() });
1352
+ return ok8({ done: false, steps, summary: `stuck: ${a.reason}`, model: creds.model });
1353
+ }
1354
+ if (a.type === "click") {
1355
+ const cx = Math.max(2, Math.min(w - 2, Math.round(a.x)));
1356
+ const cy = Math.max(2, Math.min(h - 2, Math.round(a.y)));
1357
+ await page.mouse.click(cx, cy, { stealth: true });
1358
+ acted = `click(${cx},${cy})`;
1359
+ } else if (a.type === "type") {
1360
+ await page.keyboard.type(a.text, { stealth: true });
1361
+ acted = `type("${a.text.substring(0, 30)}")`;
1362
+ } else if (a.type === "key") {
1363
+ await page.keyboard.press(a.key);
1364
+ acted = `key(${a.key})`;
1365
+ } else if (a.type === "scroll") {
1366
+ await page.mouse.wheel(0, a.direction === "down" ? a.amount : -a.amount);
1367
+ acted = `scroll(${a.direction},${a.amount})`;
1368
+ }
1369
+ } catch (e) {
1370
+ acted = `${a.type} FAILED: ${e instanceof Error ? e.message : String(e).substring(0, 80)}`;
1371
+ }
1372
+ steps.push({ n: step, thought: decision.thought, action: acted, ts: Date.now() });
1373
+ history.push(`${step}. ${acted} \u2014 ${decision.thought.substring(0, 60)}`);
1374
+ await new Promise((r) => setTimeout(r, settleMs));
1375
+ if (a.type === "click") {
1376
+ try {
1377
+ const ctxObj = page.context?.();
1378
+ const pages = ctxObj?.pages?.() || [];
1379
+ for (let i = pages.length - 1; i >= 0; i--) {
1380
+ const cand = pages[i];
1381
+ if (!cand || cand === page) continue;
1382
+ const u = cand.url?.() ?? "";
1383
+ if (!u || u === "about:blank") continue;
1384
+ if (u === page.url()) break;
1385
+ await cand.bringToFront().catch(() => {
1386
+ });
1387
+ page = cand;
1388
+ break;
1389
+ }
1390
+ } catch {
1391
+ }
1392
+ }
1393
+ const urlNow = page.url();
1394
+ if (urlNow !== lastUrl) {
1395
+ history.push(` [\u9875\u9762\u8DF3\u8F6C \u2192 ${urlNow.substring(0, 80)}]`);
1396
+ lastUrl = urlNow;
1397
+ }
1398
+ }
1399
+ return ok8({ done: false, steps, summary: `\u5DF2\u8FBE\u6700\u5927\u6B65\u6570 ${maxSteps}`, model: creds.model });
1400
+ }
1401
+ });
1402
+
1403
+ // src/commands/evaluate.ts
1404
+ import { z as z9 } from "zod";
1405
+ import { ok as ok9, normalizeTips as normalizeTips2 } from "@dyyz1993/xcli-core";
977
1406
  var evaluateCommand = registerCommand({
978
1407
  name: "eval",
979
- description: "Evaluate JavaScript expression in the browser",
1408
+ description: "Evaluate JavaScript expression in the browser (--frame <url-substring> \u53EF\u5B9A\u5411\u5230 iframe \u4E0A\u4E0B\u6587\uFF0C\u8DE8\u57DF iframe \u4EA6\u53EF)",
980
1409
  scope: "page",
981
- parameters: z7.object({
982
- expression: z7.string()
1410
+ parameters: z9.object({
1411
+ expression: z9.string().optional(),
1412
+ 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"),
1413
+ // Internal: base64url-encoded script set by the router for `eval <js...>`,
1414
+ // so multiline/semicolon JS survives chain parsing (which splits on ';'
1415
+ // and whitespace). Not intended for direct CLI use.
1416
+ "script-b64": z9.string().optional().describe("Internal: base64url script set by the CLI router; do not pass directly")
983
1417
  }),
984
1418
  handler: async (p, ctx) => {
1419
+ const rawScript = p["script-b64"];
1420
+ const expression = rawScript ? Buffer.from(rawScript, "base64url").toString("utf8") : p.expression;
1421
+ if (!expression || !expression.trim()) {
1422
+ return { ok: false, error: "eval: expression is required" };
1423
+ }
985
1424
  const engine = createRuleEngine();
986
1425
  engine.start();
987
1426
  const decision = engine.evaluate({
988
1427
  method: "Runtime.evaluate",
989
- params: { expression: p.expression },
1428
+ params: { expression },
990
1429
  sessionId: "eval-check",
991
1430
  direction: "client\u2192browser"
992
1431
  });
993
1432
  engine.stop();
994
- const result = await ctx.page.evaluate(p.expression);
995
- const response = ok7({ result });
1433
+ const result = p.frame ? await ctx.page.evaluateInFrame(p.frame, expression) : await ctx.page.evaluate(expression);
1434
+ const response = ok9({ result });
996
1435
  if (decision && decision.severity === "danger") {
997
1436
  response.tips = normalizeTips2([`${decision.suggestion}`]);
998
1437
  }
@@ -1000,37 +1439,65 @@ var evaluateCommand = registerCommand({
1000
1439
  }
1001
1440
  });
1002
1441
 
1442
+ // src/commands/ua.ts
1443
+ import { z as z10 } from "zod";
1444
+ import { ok as ok10 } from "@dyyz1993/xcli-core";
1445
+ function uaHookScript(ua) {
1446
+ const uaJson = JSON.stringify(ua);
1447
+ const appVersion = JSON.stringify(ua.replace(/^Mozilla\//, ""));
1448
+ 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){}`;
1449
+ }
1450
+ var uaCommand = registerCommand({
1451
+ name: "ua",
1452
+ description: "Get or override the session User-Agent (anti-bot UA detection countermeasure)",
1453
+ scope: "page",
1454
+ parameters: z10.object({
1455
+ 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')
1456
+ }),
1457
+ handler: async (p, ctx) => {
1458
+ if (!p.value) {
1459
+ const current = await ctx.page.evaluate("navigator.userAgent");
1460
+ return ok10({ userAgent: current });
1461
+ }
1462
+ 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;
1463
+ const script = uaHookScript(ua);
1464
+ await ctx.page.addInitScript(script);
1465
+ await ctx.page.evaluate(`(function(){${script}})()`);
1466
+ return ok10({ userAgent: ua, mode: "init-script" });
1467
+ }
1468
+ });
1469
+
1003
1470
  // src/commands/storage.ts
1004
- import { z as z8 } from "zod";
1005
- import { ok as ok8, fail as fail2 } from "@dyyz1993/xcli-core";
1471
+ import { z as z11 } from "zod";
1472
+ import { ok as ok11, fail as fail4 } from "@dyyz1993/xcli-core";
1006
1473
  var getCookiesCommand = registerCommand({
1007
1474
  name: "get-cookies",
1008
1475
  description: "Get all cookies for the current page",
1009
1476
  scope: "page",
1010
- result: z8.object({
1011
- cookies: z8.array(z8.record(z8.unknown()))
1477
+ result: z11.object({
1478
+ cookies: z11.array(z11.record(z11.unknown()))
1012
1479
  }),
1013
1480
  handler: async (_p, ctx) => {
1014
1481
  const cookies = await ctx.browserContext.cookies();
1015
- return ok8({ cookies });
1482
+ return ok11({ cookies });
1016
1483
  }
1017
1484
  });
1018
1485
  var setCookieCommand = registerCommand({
1019
1486
  name: "set-cookie",
1020
1487
  description: "Set a cookie",
1021
1488
  scope: "page",
1022
- parameters: z8.object({
1023
- name: z8.coerce.string(),
1024
- value: z8.coerce.string(),
1025
- domain: z8.coerce.string().optional(),
1026
- path: z8.coerce.string().optional(),
1027
- url: z8.string().optional().describe("Cookie URL (alternative to domain)"),
1028
- expires: z8.number().optional(),
1029
- httpOnly: z8.boolean().optional(),
1030
- secure: z8.boolean().optional(),
1031
- sameSite: z8.enum(["Strict", "Lax", "None"]).optional()
1489
+ parameters: z11.object({
1490
+ name: z11.coerce.string(),
1491
+ value: z11.coerce.string(),
1492
+ domain: z11.coerce.string().optional(),
1493
+ path: z11.coerce.string().optional(),
1494
+ url: z11.string().optional().describe("Cookie URL (alternative to domain)"),
1495
+ expires: z11.number().optional(),
1496
+ httpOnly: z11.boolean().optional(),
1497
+ secure: z11.boolean().optional(),
1498
+ sameSite: z11.enum(["Strict", "Lax", "None"]).optional()
1032
1499
  }),
1033
- result: z8.object({ name: z8.string() }),
1500
+ result: z11.object({ name: z11.string() }),
1034
1501
  handler: async (p, ctx) => {
1035
1502
  const cookie = { ...p };
1036
1503
  if (!cookie.domain && !cookie.url) {
@@ -1045,38 +1512,38 @@ var setCookieCommand = registerCommand({
1045
1512
  }
1046
1513
  }
1047
1514
  if (!cookie.domain && !cookie.url) {
1048
- return fail2("set-cookie requires --domain or --url, or a non-blank page URL to infer from");
1515
+ return fail4("set-cookie requires --domain or --url, or a non-blank page URL to infer from");
1049
1516
  }
1050
1517
  await ctx.browserContext.addCookies([cookie]);
1051
- return ok8({ name: p.name });
1518
+ return ok11({ name: p.name });
1052
1519
  }
1053
1520
  });
1054
1521
  var clearCookiesCommand = registerCommand({
1055
1522
  name: "clear-cookies",
1056
1523
  description: "Clear all cookies",
1057
1524
  scope: "page",
1058
- result: z8.object({ cleared: z8.boolean() }),
1525
+ result: z11.object({ cleared: z11.boolean() }),
1059
1526
  handler: async (_p, ctx) => {
1060
1527
  await ctx.browserContext.clearCookies();
1061
- return ok8({ cleared: true });
1528
+ return ok11({ cleared: true });
1062
1529
  }
1063
1530
  });
1064
1531
  var getLocalStorageCommand = registerCommand({
1065
1532
  name: "get-local-storage",
1066
1533
  description: "Get localStorage entries",
1067
1534
  scope: "page",
1068
- parameters: z8.object({
1069
- key: z8.string().optional()
1535
+ parameters: z11.object({
1536
+ key: z11.string().optional()
1070
1537
  }),
1071
- result: z8.union([
1072
- z8.object({ key: z8.string(), value: z8.string().nullable() }),
1073
- z8.object({ data: z8.record(z8.string()) })
1538
+ result: z11.union([
1539
+ z11.object({ key: z11.string(), value: z11.string().nullable() }),
1540
+ z11.object({ data: z11.record(z11.string()) })
1074
1541
  ]),
1075
1542
  handler: async (p, ctx) => {
1076
1543
  try {
1077
1544
  if (p.key) {
1078
1545
  const value = await ctx.page.evaluate((k) => localStorage.getItem(k), p.key);
1079
- return ok8({ key: p.key, value });
1546
+ return ok11({ key: p.key, value });
1080
1547
  }
1081
1548
  const data = await ctx.page.evaluate(() => {
1082
1549
  const entries = {};
@@ -1086,9 +1553,9 @@ var getLocalStorageCommand = registerCommand({
1086
1553
  }
1087
1554
  return entries;
1088
1555
  });
1089
- return ok8({ data });
1556
+ return ok11({ data });
1090
1557
  } catch (e) {
1091
- return fail2(`localStorage not accessible on this page: ${e instanceof Error ? e.message : String(e)}`);
1558
+ return fail4(`localStorage not accessible on this page: ${e instanceof Error ? e.message : String(e)}`);
1092
1559
  }
1093
1560
  }
1094
1561
  });
@@ -1096,11 +1563,11 @@ var setLocalStorageCommand = registerCommand({
1096
1563
  name: "set-local-storage",
1097
1564
  description: "Set a localStorage entry",
1098
1565
  scope: "page",
1099
- parameters: z8.object({
1100
- key: z8.string(),
1101
- value: z8.string()
1566
+ parameters: z11.object({
1567
+ key: z11.string(),
1568
+ value: z11.string()
1102
1569
  }),
1103
- result: z8.object({ key: z8.string() }),
1570
+ result: z11.object({ key: z11.string() }),
1104
1571
  handler: async (p, ctx) => {
1105
1572
  try {
1106
1573
  await ctx.page.evaluate(
@@ -1109,9 +1576,9 @@ var setLocalStorageCommand = registerCommand({
1109
1576
  },
1110
1577
  { key: p.key, value: p.value }
1111
1578
  );
1112
- return ok8({ key: p.key });
1579
+ return ok11({ key: p.key });
1113
1580
  } catch (e) {
1114
- return fail2(`localStorage not accessible on this page: ${e instanceof Error ? e.message : String(e)}`);
1581
+ return fail4(`localStorage not accessible on this page: ${e instanceof Error ? e.message : String(e)}`);
1115
1582
  }
1116
1583
  }
1117
1584
  });
@@ -1119,26 +1586,24 @@ var clearLocalStorageCommand = registerCommand({
1119
1586
  name: "clear-local-storage",
1120
1587
  description: "Clear all localStorage entries",
1121
1588
  scope: "page",
1122
- result: z8.object({ cleared: z8.boolean() }),
1589
+ result: z11.object({ cleared: z11.boolean() }),
1123
1590
  handler: async (_p, ctx) => {
1124
1591
  try {
1125
1592
  await ctx.page.evaluate(() => localStorage.clear());
1126
- return ok8({ cleared: true });
1593
+ return ok11({ cleared: true });
1127
1594
  } catch (e) {
1128
- return fail2(`localStorage not accessible on this page: ${e instanceof Error ? e.message : String(e)}`);
1595
+ return fail4(`localStorage not accessible on this page: ${e instanceof Error ? e.message : String(e)}`);
1129
1596
  }
1130
1597
  }
1131
1598
  });
1132
1599
 
1133
1600
  // src/commands/screenshot.ts
1134
- import { z as z9 } from "zod";
1135
- import { ok as ok9, fail as fail3 } from "@dyyz1993/xcli-core";
1601
+ import { z as z12 } from "zod";
1602
+ import { ok as ok12, fail as fail5 } from "@dyyz1993/xcli-core";
1136
1603
  import { writeFileSync, mkdirSync } from "fs";
1137
- import { dirname, join as join2 } from "path";
1138
- import { homedir as homedir2 } from "os";
1139
- var SCREENSHOTS_DIR = join2(homedir2(), ".xbrowser", "screenshots");
1604
+ import { dirname, join as join4 } from "path";
1140
1605
  function ensureScreenshotsDir() {
1141
- mkdirSync(SCREENSHOTS_DIR, { recursive: true });
1606
+ mkdirSync(resolveScreenshotsDir(), { recursive: true });
1142
1607
  }
1143
1608
  function ensureParentDir(filePath) {
1144
1609
  const dir = dirname(filePath);
@@ -1154,30 +1619,30 @@ function generateScreenshotPath(format) {
1154
1619
  const timestamp = Date.now();
1155
1620
  const random = Math.random().toString(36).slice(2, 8);
1156
1621
  const ext = format === "jpeg" ? "jpg" : "png";
1157
- return join2(SCREENSHOTS_DIR, `screenshot-${timestamp}-${random}.${ext}`);
1622
+ return join4(resolveScreenshotsDir(), `screenshot-${timestamp}-${random}.${ext}`);
1158
1623
  }
1159
1624
  var screenshotCommand = registerCommand({
1160
1625
  name: "screenshot",
1161
- description: "Take a screenshot of the page or element",
1626
+ 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>)",
1162
1627
  scope: "page",
1163
1628
  selectorParams: ["selector"],
1164
- parameters: z9.object({
1165
- selector: z9.string().optional(),
1166
- type: z9.enum(["png", "jpeg"]).optional(),
1167
- fullPage: z9.boolean().optional(),
1168
- output: z9.string().optional(),
1169
- base64: z9.boolean().optional().describe("Return base64 data instead of file path")
1629
+ parameters: z12.object({
1630
+ selector: z12.string().optional(),
1631
+ type: z12.enum(["png", "jpeg"]).optional(),
1632
+ fullPage: z12.boolean().optional(),
1633
+ output: z12.string().optional(),
1634
+ base64: z12.boolean().optional().describe("Return base64 data instead of file path")
1170
1635
  }),
1171
- result: z9.union([
1172
- z9.object({
1173
- data: z9.string(),
1174
- format: z9.string(),
1175
- size: z9.number()
1636
+ result: z12.union([
1637
+ z12.object({
1638
+ data: z12.string(),
1639
+ format: z12.string(),
1640
+ size: z12.number()
1176
1641
  }),
1177
- z9.object({
1178
- output: z9.string(),
1179
- format: z9.string(),
1180
- size: z9.number()
1642
+ z12.object({
1643
+ output: z12.string(),
1644
+ format: z12.string(),
1645
+ size: z12.number()
1181
1646
  })
1182
1647
  ]),
1183
1648
  handler: async (p, ctx) => {
@@ -1195,21 +1660,21 @@ var screenshotCommand = registerCommand({
1195
1660
  if (p.output) {
1196
1661
  const dirErr = ensureParentDir(p.output);
1197
1662
  if (dirErr) {
1198
- return fail3(`Cannot create directory for --output "${p.output}": ${dirErr}`);
1663
+ return fail5(`Cannot create directory for --output "${p.output}": ${dirErr}`);
1199
1664
  }
1200
1665
  try {
1201
1666
  writeFileSync(p.output, buffer, "binary");
1202
1667
  } catch (err) {
1203
- return fail3(`Failed to write screenshot to "${p.output}": ${err instanceof Error ? err.message : String(err)}`);
1668
+ return fail5(`Failed to write screenshot to "${p.output}": ${err instanceof Error ? err.message : String(err)}`);
1204
1669
  }
1205
- return ok9({
1670
+ return ok12({
1206
1671
  output: p.output,
1207
1672
  format,
1208
1673
  size: buffer.length
1209
1674
  });
1210
1675
  }
1211
1676
  if (p.base64) {
1212
- return ok9({
1677
+ return ok12({
1213
1678
  data: buffer.toString("base64"),
1214
1679
  format,
1215
1680
  size: buffer.length
@@ -1218,7 +1683,7 @@ var screenshotCommand = registerCommand({
1218
1683
  ensureScreenshotsDir();
1219
1684
  const screenshotPath = generateScreenshotPath(format);
1220
1685
  writeFileSync(screenshotPath, buffer, "binary");
1221
- return ok9({
1686
+ return ok12({
1222
1687
  output: screenshotPath,
1223
1688
  format,
1224
1689
  size: buffer.length
@@ -1227,19 +1692,19 @@ var screenshotCommand = registerCommand({
1227
1692
  });
1228
1693
 
1229
1694
  // src/commands/structure.ts
1230
- import { z as z10 } from "zod";
1231
- import { ok as ok10 } from "@dyyz1993/xcli-core";
1695
+ import { z as z13 } from "zod";
1696
+ import { ok as ok13 } from "@dyyz1993/xcli-core";
1232
1697
  var structureCommand = registerCommand({
1233
1698
  name: "structure",
1234
1699
  description: "Get the DOM structure of the page or an element",
1235
1700
  scope: "page",
1236
1701
  selectorParams: ["selector"],
1237
- parameters: z10.object({
1238
- selector: z10.string().optional(),
1239
- depth: z10.number().optional()
1702
+ parameters: z13.object({
1703
+ selector: z13.string().optional(),
1704
+ depth: z13.number().optional()
1240
1705
  }),
1241
- result: z10.object({
1242
- structure: z10.record(z10.unknown())
1706
+ result: z13.object({
1707
+ structure: z13.record(z13.unknown())
1243
1708
  }),
1244
1709
  handler: async (p, ctx) => {
1245
1710
  const structure = await ctx.page.evaluate(
@@ -1271,30 +1736,30 @@ var structureCommand = registerCommand({
1271
1736
  },
1272
1737
  { sel: p.selector || "body", maxDepth: p.depth || 5 }
1273
1738
  );
1274
- return ok10({ structure });
1739
+ return ok13({ structure });
1275
1740
  }
1276
1741
  });
1277
1742
 
1278
1743
  // src/commands/viewport.ts
1279
- import { z as z11 } from "zod";
1280
- import { ok as ok11 } from "@dyyz1993/xcli-core";
1744
+ import { z as z14 } from "zod";
1745
+ import { ok as ok14 } from "@dyyz1993/xcli-core";
1281
1746
  var setViewportCommand = registerCommand({
1282
1747
  name: "set-viewport",
1283
1748
  description: "Set the viewport size and properties",
1284
1749
  scope: "browser",
1285
- parameters: z11.object({
1286
- width: z11.coerce.number(),
1287
- height: z11.coerce.number(),
1288
- deviceScaleFactor: z11.coerce.number().optional(),
1289
- isMobile: z11.boolean().optional(),
1290
- hasTouch: z11.boolean().optional()
1750
+ parameters: z14.object({
1751
+ width: z14.coerce.number(),
1752
+ height: z14.coerce.number(),
1753
+ deviceScaleFactor: z14.coerce.number().optional(),
1754
+ isMobile: z14.boolean().optional(),
1755
+ hasTouch: z14.boolean().optional()
1291
1756
  }),
1292
- result: z11.object({
1293
- width: z11.number(),
1294
- height: z11.number(),
1295
- deviceScaleFactor: z11.number().optional(),
1296
- isMobile: z11.boolean().optional(),
1297
- hasTouch: z11.boolean().optional()
1757
+ result: z14.object({
1758
+ width: z14.number(),
1759
+ height: z14.number(),
1760
+ deviceScaleFactor: z14.number().optional(),
1761
+ isMobile: z14.boolean().optional(),
1762
+ hasTouch: z14.boolean().optional()
1298
1763
  }),
1299
1764
  handler: async (p, ctx) => {
1300
1765
  const viewport = ctx.page.viewportSize();
@@ -1307,7 +1772,7 @@ var setViewportCommand = registerCommand({
1307
1772
  ...p.isMobile !== void 0 && { isMobile: p.isMobile },
1308
1773
  ...p.hasTouch !== void 0 && { hasTouch: p.hasTouch }
1309
1774
  });
1310
- return ok11({
1775
+ return ok14({
1311
1776
  width,
1312
1777
  height,
1313
1778
  ...p.deviceScaleFactor !== void 0 && { deviceScaleFactor: p.deviceScaleFactor },
@@ -1318,17 +1783,17 @@ var setViewportCommand = registerCommand({
1318
1783
  });
1319
1784
 
1320
1785
  // src/commands/frame.ts
1321
- import { z as z12 } from "zod";
1322
- import { ok as ok12, fail as fail4 } from "@dyyz1993/xcli-core";
1786
+ import { z as z15 } from "zod";
1787
+ import { ok as ok15, fail as fail6 } from "@dyyz1993/xcli-core";
1323
1788
  var framesCommand = registerCommand({
1324
1789
  name: "frames",
1325
1790
  description: "List all frames in the current page",
1326
1791
  scope: "page",
1327
- result: z12.object({
1328
- frames: z12.array(z12.object({
1329
- index: z12.number(),
1330
- name: z12.string().nullable(),
1331
- url: z12.string()
1792
+ result: z15.object({
1793
+ frames: z15.array(z15.object({
1794
+ index: z15.number(),
1795
+ name: z15.string().nullable(),
1796
+ url: z15.string()
1332
1797
  }))
1333
1798
  }),
1334
1799
  handler: async (_p, ctx) => {
@@ -1339,21 +1804,21 @@ var framesCommand = registerCommand({
1339
1804
  name: frame.name(),
1340
1805
  url: frame.url()
1341
1806
  }));
1342
- return ok12({ frames: frameList });
1807
+ return ok15({ frames: frameList });
1343
1808
  }
1344
1809
  });
1345
1810
  var frameCommand = registerCommand({
1346
1811
  name: "frame",
1347
1812
  description: "Get frame info by index or name",
1348
1813
  scope: "page",
1349
- parameters: z12.object({
1350
- index: z12.number().int().min(0).optional(),
1351
- name: z12.string().optional()
1814
+ parameters: z15.object({
1815
+ index: z15.number().int().min(0).optional(),
1816
+ name: z15.string().optional()
1352
1817
  }),
1353
- result: z12.object({
1354
- name: z12.string().nullable(),
1355
- url: z12.string(),
1356
- error: z12.string().optional()
1818
+ result: z15.object({
1819
+ name: z15.string().nullable(),
1820
+ url: z15.string(),
1821
+ error: z15.string().optional()
1357
1822
  }),
1358
1823
  handler: async (p, ctx) => {
1359
1824
  const discover = ctx.page.discoverFrames;
@@ -1364,12 +1829,12 @@ var frameCommand = registerCommand({
1364
1829
  } else if (p.name !== void 0) {
1365
1830
  targetFrame = rawFrames.find((f) => f.name() === p.name);
1366
1831
  } else {
1367
- return fail4("Must provide index or name");
1832
+ return fail6("Must provide index or name");
1368
1833
  }
1369
1834
  if (!targetFrame) {
1370
- return fail4("Frame not found");
1835
+ return fail6("Frame not found");
1371
1836
  }
1372
- return ok12({
1837
+ return ok15({
1373
1838
  name: targetFrame.name(),
1374
1839
  url: targetFrame.url()
1375
1840
  });
@@ -1377,27 +1842,27 @@ var frameCommand = registerCommand({
1377
1842
  });
1378
1843
 
1379
1844
  // src/commands/ui-debug.ts
1380
- import { z as z13 } from "zod";
1381
- import { ok as ok13 } from "@dyyz1993/xcli-core";
1845
+ import { z as z16 } from "zod";
1846
+ import { ok as ok16 } from "@dyyz1993/xcli-core";
1382
1847
  var consoleCheckCommand = registerCommand({
1383
1848
  name: "console",
1384
1849
  description: "Collect and analyze browser console messages (errors, warnings, logs)",
1385
1850
  scope: "page",
1386
- parameters: z13.object({
1387
- url: z13.string().optional().describe("URL to navigate first (optional, uses current page if omitted)"),
1388
- duration: z13.number().optional().default(5e3).describe("How long to collect messages (ms)"),
1389
- filter: z13.enum(["all", "error", "warning", "info", "log"]).optional().default("all"),
1390
- includeStackTraces: z13.boolean().optional().default(true)
1851
+ parameters: z16.object({
1852
+ url: z16.string().optional().describe("URL to navigate first (optional, uses current page if omitted)"),
1853
+ duration: z16.number().optional().default(5e3).describe("How long to collect messages (ms)"),
1854
+ filter: z16.enum(["all", "error", "warning", "info", "log"]).optional().default("all"),
1855
+ includeStackTraces: z16.boolean().optional().default(true)
1391
1856
  }),
1392
- result: z13.object({
1393
- url: z13.string(),
1394
- duration: z13.number(),
1395
- total: z13.number(),
1396
- errors: z13.number(),
1397
- warnings: z13.number(),
1398
- messages: z13.array(z13.record(z13.unknown())),
1399
- summary: z13.string(),
1400
- passed: z13.boolean()
1857
+ result: z16.object({
1858
+ url: z16.string(),
1859
+ duration: z16.number(),
1860
+ total: z16.number(),
1861
+ errors: z16.number(),
1862
+ warnings: z16.number(),
1863
+ messages: z16.array(z16.record(z16.unknown())),
1864
+ summary: z16.string(),
1865
+ passed: z16.boolean()
1401
1866
  }),
1402
1867
  handler: async (p, ctx) => {
1403
1868
  const { page } = ctx;
@@ -1479,7 +1944,7 @@ ${a.stack || ""}`;
1479
1944
  const filtered = p.filter === "all" ? messages : messages.filter((m) => m.type === p.filter);
1480
1945
  const errorCount = messages.filter((m) => m.type === "error").length;
1481
1946
  const warnCount = messages.filter((m) => m.type === "warning").length;
1482
- return ok13({
1947
+ return ok16({
1483
1948
  url: page.url(),
1484
1949
  duration: p.duration,
1485
1950
  total: messages.length,
@@ -1495,23 +1960,23 @@ var networkCheckCommand = registerCommand({
1495
1960
  name: "net-debug",
1496
1961
  description: "Monitor and analyze network requests \u2014 capture failed requests, slow responses, status codes",
1497
1962
  scope: "page",
1498
- parameters: z13.object({
1499
- url: z13.string().optional().describe("URL to navigate first"),
1500
- duration: z13.number().optional().default(5e3).describe("How long to monitor (ms)"),
1501
- filter: z13.enum(["all", "failed", "slow", "error", "xhr", "fetch", "document", "stylesheet", "script", "image"]).optional().default("all"),
1502
- slowThreshold: z13.number().optional().default(3e3).describe('Threshold for "slow" requests (ms)')
1963
+ parameters: z16.object({
1964
+ url: z16.string().optional().describe("URL to navigate first"),
1965
+ duration: z16.number().optional().default(5e3).describe("How long to monitor (ms)"),
1966
+ filter: z16.enum(["all", "failed", "slow", "error", "xhr", "fetch", "document", "stylesheet", "script", "image"]).optional().default("all"),
1967
+ slowThreshold: z16.number().optional().default(3e3).describe('Threshold for "slow" requests (ms)')
1503
1968
  }),
1504
- result: z13.object({
1505
- url: z13.string(),
1506
- duration: z13.number(),
1507
- totalRequests: z13.number(),
1508
- failedRequests: z13.number(),
1509
- slowRequests: z13.number(),
1510
- errorRequests: z13.number(),
1511
- totalSizeKB: z13.number(),
1512
- requests: z13.array(z13.record(z13.unknown())),
1513
- summary: z13.string(),
1514
- passed: z13.boolean()
1969
+ result: z16.object({
1970
+ url: z16.string(),
1971
+ duration: z16.number(),
1972
+ totalRequests: z16.number(),
1973
+ failedRequests: z16.number(),
1974
+ slowRequests: z16.number(),
1975
+ errorRequests: z16.number(),
1976
+ totalSizeKB: z16.number(),
1977
+ requests: z16.array(z16.record(z16.unknown())),
1978
+ summary: z16.string(),
1979
+ passed: z16.boolean()
1515
1980
  }),
1516
1981
  handler: async (p, ctx) => {
1517
1982
  const { page } = ctx;
@@ -1593,7 +2058,7 @@ var networkCheckCommand = registerCommand({
1593
2058
  const slowCount = requests.filter((r) => r.duration >= p.slowThreshold).length;
1594
2059
  const errorCount = requests.filter((r) => r.error).length;
1595
2060
  const totalSize = requests.reduce((sum, r) => sum + r.size, 0);
1596
- return ok13({
2061
+ return ok16({
1597
2062
  url: page.url(),
1598
2063
  duration: p.duration,
1599
2064
  totalRequests: requests.length,
@@ -1615,17 +2080,17 @@ var perfCheckCommand = registerCommand({
1615
2080
  name: "perf",
1616
2081
  description: "Audit page performance metrics \u2014 load time, FCP, LCP, CLS, TTFB, resource sizes",
1617
2082
  scope: "page",
1618
- parameters: z13.object({
1619
- url: z13.string().optional().describe("URL to navigate (uses current page if omitted)"),
1620
- iterations: z13.number().optional().default(1).describe("Number of iterations to average")
2083
+ parameters: z16.object({
2084
+ url: z16.string().optional().describe("URL to navigate (uses current page if omitted)"),
2085
+ iterations: z16.number().optional().default(1).describe("Number of iterations to average")
1621
2086
  }),
1622
- result: z13.object({
1623
- url: z13.string(),
1624
- iterations: z13.number(),
1625
- metrics: z13.record(z13.unknown()),
1626
- allIterations: z13.array(z13.record(z13.unknown())).optional(),
1627
- passed: z13.boolean(),
1628
- summary: z13.string()
2087
+ result: z16.object({
2088
+ url: z16.string(),
2089
+ iterations: z16.number(),
2090
+ metrics: z16.record(z16.unknown()),
2091
+ allIterations: z16.array(z16.record(z16.unknown())).optional(),
2092
+ passed: z16.boolean(),
2093
+ summary: z16.string()
1629
2094
  }),
1630
2095
  handler: async (p, ctx) => {
1631
2096
  const { page } = ctx;
@@ -1687,7 +2152,7 @@ var perfCheckCommand = registerCommand({
1687
2152
  if (resourceStats) avg.resourceStats = resourceStats;
1688
2153
  return avg;
1689
2154
  })();
1690
- return ok13({
2155
+ return ok16({
1691
2156
  url: page.url(),
1692
2157
  iterations: p.iterations,
1693
2158
  metrics: avgMetrics,
@@ -1701,23 +2166,23 @@ var healthCheckCommand = registerCommand({
1701
2166
  name: "health",
1702
2167
  description: "Comprehensive page health check \u2014 broken links, missing images, console errors, SEO issues",
1703
2168
  scope: "page",
1704
- parameters: z13.object({
1705
- url: z13.string().optional().describe("URL to check"),
1706
- checkLinks: z13.boolean().optional().default(true).describe("Check for broken links"),
1707
- checkImages: z13.boolean().optional().default(true).describe("Check for missing/broken images"),
1708
- checkMeta: z13.boolean().optional().default(true).describe("Check SEO meta tags"),
1709
- maxLinks: z13.number().optional().default(50).describe("Max links to check")
2169
+ parameters: z16.object({
2170
+ url: z16.string().optional().describe("URL to check"),
2171
+ checkLinks: z16.boolean().optional().default(true).describe("Check for broken links"),
2172
+ checkImages: z16.boolean().optional().default(true).describe("Check for missing/broken images"),
2173
+ checkMeta: z16.boolean().optional().default(true).describe("Check SEO meta tags"),
2174
+ maxLinks: z16.number().optional().default(50).describe("Max links to check")
1710
2175
  }),
1711
- result: z13.object({
1712
- url: z13.string(),
1713
- title: z13.string(),
1714
- passed: z13.boolean(),
1715
- totalIssues: z13.number(),
1716
- errors: z13.number(),
1717
- warnings: z13.number(),
1718
- info: z13.number(),
1719
- issues: z13.array(z13.record(z13.unknown())),
1720
- summary: z13.string()
2176
+ result: z16.object({
2177
+ url: z16.string(),
2178
+ title: z16.string(),
2179
+ passed: z16.boolean(),
2180
+ totalIssues: z16.number(),
2181
+ errors: z16.number(),
2182
+ warnings: z16.number(),
2183
+ info: z16.number(),
2184
+ issues: z16.array(z16.record(z16.unknown())),
2185
+ summary: z16.string()
1721
2186
  }),
1722
2187
  handler: async (p, ctx) => {
1723
2188
  const { page } = ctx;
@@ -1804,7 +2269,7 @@ var healthCheckCommand = registerCommand({
1804
2269
  }, { checkLinks: p.checkLinks, checkImages: p.checkImages, checkMeta: p.checkMeta, maxLinks: p.maxLinks });
1805
2270
  const errors = result.issues.filter((i) => i.severity === "error").length;
1806
2271
  const warnings = result.issues.filter((i) => i.severity === "warning").length;
1807
- return ok13({
2272
+ return ok16({
1808
2273
  url: result.url,
1809
2274
  title: result.title,
1810
2275
  passed: errors === 0,
@@ -1819,68 +2284,66 @@ var healthCheckCommand = registerCommand({
1819
2284
  });
1820
2285
 
1821
2286
  // src/commands/actions.ts
1822
- import { z as z14 } from "zod";
1823
- import { ok as ok14 } from "@dyyz1993/xcli-core";
2287
+ import { z as z17 } from "zod";
2288
+ import { ok as ok17 } from "@dyyz1993/xcli-core";
1824
2289
  import { writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
1825
- import { join as join3 } from "path";
1826
- import { homedir as homedir3 } from "os";
1827
- var SCREENSHOTS_DIR2 = join3(homedir3(), ".xbrowser", "screenshots");
2290
+ import { join as join5 } from "path";
1828
2291
  function ensureScreenshotsDir2() {
1829
- mkdirSync2(SCREENSHOTS_DIR2, { recursive: true });
2292
+ mkdirSync2(resolveScreenshotsDir(), { recursive: true });
1830
2293
  }
1831
2294
  function generateScreenshotPath2(format) {
1832
2295
  const timestamp = Date.now();
1833
2296
  const random = Math.random().toString(36).slice(2, 8);
1834
2297
  const ext = format === "jpeg" ? "jpg" : "png";
1835
- return join3(SCREENSHOTS_DIR2, `screenshot-${timestamp}-${random}.${ext}`);
2298
+ return join5(resolveScreenshotsDir(), `screenshot-${timestamp}-${random}.${ext}`);
1836
2299
  }
1837
- var waitActionSchema = z14.object({
1838
- type: z14.literal("wait"),
1839
- milliseconds: z14.number().positive().optional(),
1840
- selector: z14.string().optional()
2300
+ var waitActionSchema = z17.object({
2301
+ type: z17.literal("wait"),
2302
+ milliseconds: z17.number().positive().optional(),
2303
+ selector: z17.string().optional()
1841
2304
  }).refine(
1842
2305
  (d) => (d.milliseconds !== void 0 || d.selector !== void 0) && !(d.milliseconds !== void 0 && d.selector !== void 0),
1843
2306
  { message: "Either 'milliseconds' or 'selector' must be provided, but not both." }
1844
2307
  );
1845
- var clickActionSchema = z14.object({
1846
- type: z14.literal("click"),
1847
- selector: z14.string(),
1848
- all: z14.boolean().optional()
2308
+ var clickActionSchema = z17.object({
2309
+ type: z17.literal("click"),
2310
+ selector: z17.string(),
2311
+ all: z17.boolean().optional()
1849
2312
  });
1850
- var screenshotActionSchema = z14.object({
1851
- type: z14.literal("screenshot"),
1852
- fullPage: z14.boolean().optional(),
1853
- quality: z14.number().min(1).max(100).optional(),
1854
- viewport: z14.object({ width: z14.number().int().positive(), height: z14.number().int().positive() }).optional(),
1855
- base64: z14.boolean().optional().describe("Return base64 data instead of file path")
2313
+ var screenshotActionSchema = z17.object({
2314
+ type: z17.literal("screenshot"),
2315
+ fullPage: z17.boolean().optional(),
2316
+ quality: z17.number().min(1).max(100).optional(),
2317
+ viewport: z17.object({ width: z17.number().int().positive(), height: z17.number().int().positive() }).optional(),
2318
+ base64: z17.boolean().optional().describe("Return base64 data instead of file path")
1856
2319
  });
1857
- var writeActionSchema = z14.object({
1858
- type: z14.literal("write"),
1859
- text: z14.string()
2320
+ var writeActionSchema = z17.object({
2321
+ type: z17.literal("write"),
2322
+ text: z17.string()
1860
2323
  });
1861
- var pressActionSchema = z14.object({
1862
- type: z14.literal("press"),
1863
- key: z14.string()
2324
+ var pressActionSchema = z17.object({
2325
+ type: z17.literal("press"),
2326
+ key: z17.string()
1864
2327
  });
1865
- var scrollActionSchema = z14.object({
1866
- type: z14.literal("scroll"),
1867
- direction: z14.enum(["up", "down"]).optional(),
1868
- selector: z14.string().optional()
2328
+ var scrollActionSchema = z17.object({
2329
+ type: z17.literal("scroll"),
2330
+ direction: z17.enum(["up", "down"]).optional(),
2331
+ selector: z17.string().optional()
1869
2332
  });
1870
- var scrapeActionSchema = z14.object({
1871
- type: z14.literal("scrape")
2333
+ var scrapeActionSchema = z17.object({
2334
+ type: z17.literal("scrape")
1872
2335
  });
1873
- var executeJavascriptActionSchema = z14.object({
1874
- type: z14.literal("executeJavascript"),
1875
- script: z14.string()
2336
+ var executeJavascriptActionSchema = z17.object({
2337
+ type: z17.literal("executeJavascript"),
2338
+ script: z17.string()
1876
2339
  });
1877
- var pdfActionSchema = z14.object({
1878
- type: z14.literal("pdf"),
1879
- landscape: z14.boolean().optional(),
1880
- scale: z14.number().optional(),
1881
- format: z14.enum(["A0", "A1", "A2", "A3", "A4", "A5", "A6", "Letter", "Legal", "Tabloid", "Ledger"]).optional()
2340
+ var pdfActionSchema = z17.object({
2341
+ type: z17.literal("pdf"),
2342
+ landscape: z17.boolean().optional(),
2343
+ scale: z17.number().optional(),
2344
+ format: z17.enum(["A0", "A1", "A2", "A3", "A4", "A5", "A6", "Letter", "Legal", "Tabloid", "Ledger"]).optional()
1882
2345
  });
1883
- var actionSchema = z14.union([
2346
+ var actionSchema = z17.union([
1884
2347
  waitActionSchema,
1885
2348
  clickActionSchema,
1886
2349
  screenshotActionSchema,
@@ -1971,11 +2434,11 @@ var actionsCommand = registerCommand({
1971
2434
  name: "actions",
1972
2435
  description: "Execute a sequence of actions (wait, click, scroll, screenshot, fill, etc.)",
1973
2436
  scope: "page",
1974
- parameters: z14.object({
1975
- url: z14.string().describe("Starting URL"),
1976
- actions: z14.array(actionSchema).max(MAX_ACTIONS).describe("Array of actions (max 50)"),
1977
- output: z14.enum(["text", "json"]).default("json").describe("Output format: text or json"),
1978
- timeout: z14.number().default(60).describe("Overall timeout in seconds (default: 60)")
2437
+ parameters: z17.object({
2438
+ url: z17.string().describe("Starting URL"),
2439
+ actions: z17.array(actionSchema).max(MAX_ACTIONS).describe("Array of actions (max 50)"),
2440
+ output: z17.enum(["text", "json"]).default("json").describe("Output format: text or json"),
2441
+ timeout: z17.number().default(60).describe("Overall timeout in seconds (default: 60)")
1979
2442
  }),
1980
2443
  handler: async (p, ctx) => {
1981
2444
  await ctx.page.goto(p.url, { waitUntil: "domcontentloaded" });
@@ -1995,14 +2458,14 @@ var actionsCommand = registerCommand({
1995
2458
  const finalUrl = ctx.page.url();
1996
2459
  const timedOut = results.length < p.actions.length;
1997
2460
  if (p.output === "text") {
1998
- return ok14({
2461
+ return ok17({
1999
2462
  title,
2000
2463
  url: finalUrl,
2001
2464
  actions: results.map((r) => JSON.stringify(r)).join("\n"),
2002
2465
  ...timedOut ? { warning: `Timed out after ${p.timeout}s, completed ${results.length}/${p.actions.length} actions` } : {}
2003
2466
  });
2004
2467
  }
2005
- return ok14({
2468
+ return ok17({
2006
2469
  title,
2007
2470
  url: finalUrl,
2008
2471
  results,
@@ -2012,8 +2475,8 @@ var actionsCommand = registerCommand({
2012
2475
  });
2013
2476
 
2014
2477
  // src/commands/scrape.ts
2015
- import { z as z15 } from "zod";
2016
- import { ok as ok15, fail as fail5 } from "@dyyz1993/xcli-core";
2478
+ import { z as z18 } from "zod";
2479
+ import { ok as ok18, fail as fail7 } from "@dyyz1993/xcli-core";
2017
2480
 
2018
2481
  // src/lib/html-to-markdown.ts
2019
2482
  import * as cheerio from "cheerio";
@@ -2398,15 +2861,15 @@ var scrapeCommand = registerCommand({
2398
2861
  description: "Scrape a page and convert to Markdown (with JS rendering)",
2399
2862
  scope: "project",
2400
2863
  selectorParams: ["selector"],
2401
- parameters: z15.object({
2402
- url: z15.string().optional(),
2403
- selector: z15.string().optional(),
2404
- timeout: z15.number().default(3e4),
2405
- format: z15.enum(["markdown", "html", "text"]).default("markdown"),
2406
- onlyMainContent: z15.boolean().default(true),
2407
- retries: z15.number().int().min(0).max(5).optional().default(2).describe("\u91CD\u8BD5\u6B21\u6570\uFF08\u9ED8\u8BA4 2\uFF09"),
2408
- waitAfterLoad: z15.number().int().optional().default(0).describe("\u9875\u9762\u52A0\u8F7D\u540E\u989D\u5916\u7B49\u5F85\u6BEB\u79D2"),
2409
- 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")
2864
+ parameters: z18.object({
2865
+ url: z18.string().optional(),
2866
+ selector: z18.string().optional(),
2867
+ timeout: z18.number().default(3e4),
2868
+ format: z18.enum(["markdown", "html", "text"]).default("markdown"),
2869
+ onlyMainContent: z18.boolean().default(true),
2870
+ retries: z18.number().int().min(0).max(5).optional().default(2).describe("\u91CD\u8BD5\u6B21\u6570\uFF08\u9ED8\u8BA4 2\uFF09"),
2871
+ waitAfterLoad: z18.number().int().optional().default(0).describe("\u9875\u9762\u52A0\u8F7D\u540E\u989D\u5916\u7B49\u5F85\u6BEB\u79D2"),
2872
+ 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")
2410
2873
  }),
2411
2874
  handler: async (p, ctx) => {
2412
2875
  const { context, page } = await createEphemeralContext(resolveLaunchOpts(ctx));
@@ -2414,7 +2877,7 @@ var scrapeCommand = registerCommand({
2414
2877
  try {
2415
2878
  const targetUrl = p.url || page.url();
2416
2879
  if (!targetUrl || targetUrl === "about:blank") {
2417
- return fail5("URL is required. Provide a URL or connect to a browser (--cdp) with a non-blank page.");
2880
+ return fail7("URL is required. Provide a URL or connect to a browser (--cdp) with a non-blank page.");
2418
2881
  }
2419
2882
  let lastError;
2420
2883
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
@@ -2484,7 +2947,7 @@ var scrapeCommand = registerCommand({
2484
2947
  } catch {
2485
2948
  }
2486
2949
  if (p.mode === "smart") {
2487
- return ok15(structured);
2950
+ return ok18(structured);
2488
2951
  }
2489
2952
  if (p.mode === "compact") {
2490
2953
  const compactData = structured.tables.length > 0 ? structured.tables.map((t) => ({
@@ -2497,9 +2960,9 @@ var scrapeCommand = registerCommand({
2497
2960
  return compact;
2498
2961
  })
2499
2962
  })) : structured.mainText?.substring(0, 500);
2500
- return ok15({ url: structured.url, title: structured.title, data: compactData });
2963
+ return ok18({ url: structured.url, title: structured.title, data: compactData });
2501
2964
  }
2502
- return ok15(structured);
2965
+ return ok18(structured);
2503
2966
  }
2504
2967
  let content;
2505
2968
  switch (p.format) {
@@ -2558,7 +3021,7 @@ var scrapeCommand = registerCommand({
2558
3021
  content = await page.innerText("body");
2559
3022
  break;
2560
3023
  }
2561
- return ok15({ content, title, url: finalUrl });
3024
+ return ok18({ content, title, url: finalUrl });
2562
3025
  } catch (err) {
2563
3026
  lastError = err instanceof Error ? err : new Error(String(err));
2564
3027
  if (attempt < maxAttempts) {
@@ -2568,20 +3031,20 @@ var scrapeCommand = registerCommand({
2568
3031
  }
2569
3032
  }
2570
3033
  }
2571
- return fail5(`Scrape failed after ${maxAttempts} attempt(s): ${lastError?.message ?? "unknown error"}`);
3034
+ return fail7(`Scrape failed after ${maxAttempts} attempt(s): ${lastError?.message ?? "unknown error"}`);
2572
3035
  } finally {
2573
3036
  await closeEphemeralContext(context);
2574
3037
  }
2575
3038
  },
2576
- result: z15.object({
2577
- url: z15.string(),
2578
- title: z15.string()
3039
+ result: z18.object({
3040
+ url: z18.string(),
3041
+ title: z18.string()
2579
3042
  }).passthrough()
2580
3043
  });
2581
3044
 
2582
3045
  // src/commands/map.ts
2583
- import { z as z16 } from "zod";
2584
- import { ok as ok16, fail as fail6 } from "@dyyz1993/xcli-core";
3046
+ import { z as z19 } from "zod";
3047
+ import { ok as ok19, fail as fail8 } from "@dyyz1993/xcli-core";
2585
3048
 
2586
3049
  // src/utils/url.ts
2587
3050
  var SKIP_EXTENSIONS = /* @__PURE__ */ new Set([
@@ -2816,21 +3279,21 @@ var mapCommand = registerCommand({
2816
3279
  name: "map",
2817
3280
  description: "Discover all URLs on a website via sitemap and page link extraction",
2818
3281
  scope: "project",
2819
- parameters: z16.object({
2820
- url: z16.string().optional(),
2821
- search: z16.string().optional(),
2822
- sitemap: z16.enum(["include", "only"]).optional(),
2823
- includeSubdomains: z16.boolean().optional(),
2824
- allowExternalLinks: z16.boolean().optional().describe("Include links to external domains"),
2825
- limit: z16.number().optional(),
2826
- verbose: z16.boolean().default(false).describe("Show progress feedback")
3282
+ parameters: z19.object({
3283
+ url: z19.string().optional(),
3284
+ search: z19.string().optional(),
3285
+ sitemap: z19.enum(["include", "only"]).optional(),
3286
+ includeSubdomains: z19.boolean().optional(),
3287
+ allowExternalLinks: z19.boolean().optional().describe("Include links to external domains"),
3288
+ limit: z19.number().optional(),
3289
+ verbose: z19.boolean().default(false).describe("Show progress feedback")
2827
3290
  }),
2828
3291
  handler: async (p, ctx) => {
2829
3292
  const { context, page } = await createEphemeralContext(resolveLaunchOpts(ctx));
2830
3293
  try {
2831
3294
  const targetUrl = p.url || page.url();
2832
3295
  if (!targetUrl || targetUrl === "about:blank") {
2833
- return fail6("URL is required. Provide a URL or connect to a browser (--cdp) with a non-blank page.");
3296
+ return fail8("URL is required. Provide a URL or connect to a browser (--cdp) with a non-blank page.");
2834
3297
  }
2835
3298
  const links = await discoverUrls(page, targetUrl, {
2836
3299
  sitemap: p.sitemap,
@@ -2841,7 +3304,7 @@ var mapCommand = registerCommand({
2841
3304
  verbose: p.verbose
2842
3305
  });
2843
3306
  const linkObjects = links.map((url) => ({ url }));
2844
- return ok16({
3307
+ return ok19({
2845
3308
  links: linkObjects,
2846
3309
  success: true
2847
3310
  });
@@ -2849,15 +3312,15 @@ var mapCommand = registerCommand({
2849
3312
  await closeEphemeralContext(context);
2850
3313
  }
2851
3314
  },
2852
- result: z16.object({
2853
- links: z16.array(z16.object({ url: z16.string() })),
2854
- success: z16.boolean()
3315
+ result: z19.object({
3316
+ links: z19.array(z19.object({ url: z19.string() })),
3317
+ success: z19.boolean()
2855
3318
  })
2856
3319
  });
2857
3320
 
2858
3321
  // src/commands/crawl.ts
2859
- import { z as z17 } from "zod";
2860
- import { ok as ok17 } from "@dyyz1993/xcli-core";
3322
+ import { z as z20 } from "zod";
3323
+ import { ok as ok20 } from "@dyyz1993/xcli-core";
2861
3324
  function stripHashAnchorQuery(url) {
2862
3325
  try {
2863
3326
  const parsed = new URL(url);
@@ -3034,21 +3497,21 @@ var crawlCommand = registerCommand({
3034
3497
  name: "crawl",
3035
3498
  description: "Crawl a website and extract content from all pages",
3036
3499
  scope: "project",
3037
- parameters: z17.object({
3038
- url: z17.string(),
3039
- limit: z17.number().default(10),
3040
- maxDepth: z17.number().default(3),
3041
- includePaths: z17.array(z17.string()).optional(),
3042
- excludePaths: z17.array(z17.string()).optional(),
3043
- allowSubdomains: z17.boolean().default(false),
3044
- allowExternalLinks: z17.boolean().default(false),
3045
- allowBackwardCrawling: z17.boolean().default(false),
3046
- enableSpa: z17.boolean().default(true).describe("Disable to skip SPA route detection"),
3047
- format: z17.enum(["markdown", "html"]).default("markdown"),
3048
- onlyMainContent: z17.boolean().default(true),
3049
- concurrency: z17.number().default(3),
3050
- retries: z17.number().default(2),
3051
- verbose: z17.boolean().default(false)
3500
+ parameters: z20.object({
3501
+ url: z20.string(),
3502
+ limit: z20.number().default(10),
3503
+ maxDepth: z20.number().default(3),
3504
+ includePaths: z20.array(z20.string()).optional(),
3505
+ excludePaths: z20.array(z20.string()).optional(),
3506
+ allowSubdomains: z20.boolean().default(false),
3507
+ allowExternalLinks: z20.boolean().default(false),
3508
+ allowBackwardCrawling: z20.boolean().default(false),
3509
+ enableSpa: z20.boolean().default(true).describe("Disable to skip SPA route detection"),
3510
+ format: z20.enum(["markdown", "html"]).default("markdown"),
3511
+ onlyMainContent: z20.boolean().default(true),
3512
+ concurrency: z20.number().default(3),
3513
+ retries: z20.number().default(2),
3514
+ verbose: z20.boolean().default(false)
3052
3515
  }),
3053
3516
  handler: async (p, ctx) => {
3054
3517
  const startUrl = new URL(p.url);
@@ -3187,7 +3650,7 @@ var crawlCommand = registerCommand({
3187
3650
  if (errorPages.length > 0) {
3188
3651
  response.errors = errorPages;
3189
3652
  }
3190
- return ok17(response);
3653
+ return ok20(response);
3191
3654
  } finally {
3192
3655
  for (const ctx2 of contexts) {
3193
3656
  await ctx2.close().catch(() => {
@@ -3197,15 +3660,15 @@ var crawlCommand = registerCommand({
3197
3660
  } catch (err) {
3198
3661
  const message = err instanceof Error ? err.message : String(err);
3199
3662
  const successPages = results.filter((r) => !isPageError(r));
3200
- return ok17({ pages: successPages, total: successPages.length, success: false, error: message });
3663
+ return ok20({ pages: successPages, total: successPages.length, success: false, error: message });
3201
3664
  }
3202
3665
  }
3203
3666
  });
3204
3667
 
3205
3668
  // src/commands/search.ts
3206
- import { z as z18 } from "zod";
3669
+ import { z as z21 } from "zod";
3207
3670
  import * as cheerio2 from "cheerio";
3208
- import { ok as ok18 } from "@dyyz1993/xcli-core";
3671
+ import { ok as ok21 } from "@dyyz1993/xcli-core";
3209
3672
  function getRecencyParams(recency) {
3210
3673
  const now = Math.floor(Date.now() / 1e3);
3211
3674
  switch (recency) {
@@ -3539,16 +4002,16 @@ var searchCommand = registerCommand({
3539
4002
  name: "search",
3540
4003
  description: "Search the web and extract results with engine fallback",
3541
4004
  scope: "project",
3542
- parameters: z18.object({
3543
- query: z18.string(),
3544
- engine: z18.string().optional(),
3545
- limit: z18.number().default(10),
3546
- full: z18.boolean().default(false),
3547
- format: z18.enum(["markdown", "json", "text"]).default("markdown"),
3548
- timeout: z18.number().default(15e3),
3549
- recency: z18.enum(["hour", "day", "week", "month", "year"]).optional().describe("Filter by time: hour/day/week/month/year"),
3550
- fallback: z18.boolean().default(false).describe("Sequential engine fallback instead of parallel"),
3551
- site: z18.string().optional().describe("Limit results to a specific site (e.g. github.com, v2ex.com)")
4005
+ parameters: z21.object({
4006
+ query: z21.string(),
4007
+ engine: z21.string().optional(),
4008
+ limit: z21.number().default(10),
4009
+ full: z21.boolean().default(false),
4010
+ format: z21.enum(["markdown", "json", "text"]).default("markdown"),
4011
+ timeout: z21.number().default(15e3),
4012
+ recency: z21.enum(["hour", "day", "week", "month", "year"]).optional().describe("Filter by time: hour/day/week/month/year"),
4013
+ fallback: z21.boolean().default(false).describe("Sequential engine fallback instead of parallel"),
4014
+ site: z21.string().optional().describe("Limit results to a specific site (e.g. github.com, v2ex.com)")
3552
4015
  }),
3553
4016
  handler: async (p, ctx) => {
3554
4017
  const { context } = await createEphemeralContext(resolveLaunchOpts(ctx));
@@ -3654,7 +4117,7 @@ ${errors.map((e) => ` - ${e.engine}: ${e.error}`).join("\n")}`
3654
4117
  lines.push(`> ${r.snippet}`);
3655
4118
  lines.push("");
3656
4119
  }
3657
- return ok18({ ...searchResult, content: lines.join("\n") });
4120
+ return ok21({ ...searchResult, content: lines.join("\n") });
3658
4121
  }
3659
4122
  if (p.format === "text") {
3660
4123
  const lines = [`Search: ${searchResult.query} (Engine: ${searchResult.engine}, Total: ${searchResult.total})`, ""];
@@ -3664,9 +4127,9 @@ ${errors.map((e) => ` - ${e.engine}: ${e.error}`).join("\n")}`
3664
4127
  lines.push(` ${r.snippet}`);
3665
4128
  lines.push("");
3666
4129
  }
3667
- return ok18({ ...searchResult, content: lines.join("\n") });
4130
+ return ok21({ ...searchResult, content: lines.join("\n") });
3668
4131
  }
3669
- return ok18(searchResult);
4132
+ return ok21(searchResult);
3670
4133
  } finally {
3671
4134
  await closeEphemeralContext(context);
3672
4135
  }
@@ -3674,8 +4137,8 @@ ${errors.map((e) => ` - ${e.engine}: ${e.error}`).join("\n")}`
3674
4137
  });
3675
4138
 
3676
4139
  // src/commands/network.ts
3677
- import { z as z19 } from "zod";
3678
- import { ok as ok19, fail as fail7 } from "@dyyz1993/xcli-core";
4140
+ import { z as z22 } from "zod";
4141
+ import { ok as ok22, fail as fail9 } from "@dyyz1993/xcli-core";
3679
4142
  function extractPath2(url) {
3680
4143
  try {
3681
4144
  const u = new URL(url);
@@ -3773,19 +4236,19 @@ var networkCommand = registerCommand({
3773
4236
  name: "network",
3774
4237
  description: "Capture and filter network responses from a URL",
3775
4238
  scope: "project",
3776
- parameters: z19.object({
3777
- url: z19.string(),
3778
- filter: z19.string().optional(),
3779
- match: z19.string().optional(),
3780
- search: z19.string().optional().describe("Search within all captured response bodies"),
3781
- console: z19.boolean().default(false),
3782
- timeout: z19.number().default(3e4),
3783
- wait: z19.number().default(3e3),
3784
- limit: z19.number().default(50),
3785
- format: z19.enum(["summary", "json"]).default("summary"),
3786
- ws: z19.boolean().optional().default(false).describe("Only show WebSocket messages"),
3787
- 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"),
3788
- duration: z19.number().int().optional().default(15e3).describe("\u76D1\u542C\u6A21\u5F0F\u7B49\u5F85\u65F6\u957F\uFF08\u6BEB\u79D2\uFF0C\u9ED8\u8BA4 15000\uFF09")
4239
+ parameters: z22.object({
4240
+ url: z22.string(),
4241
+ filter: z22.string().optional(),
4242
+ match: z22.string().optional(),
4243
+ search: z22.string().optional().describe("Search within all captured response bodies"),
4244
+ console: z22.boolean().default(false),
4245
+ timeout: z22.number().default(3e4),
4246
+ wait: z22.number().default(3e3),
4247
+ limit: z22.number().default(50),
4248
+ format: z22.enum(["summary", "json"]).default("summary"),
4249
+ ws: z22.boolean().optional().default(false).describe("Only show WebSocket messages"),
4250
+ 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"),
4251
+ duration: z22.number().int().optional().default(15e3).describe("\u76D1\u542C\u6A21\u5F0F\u7B49\u5F85\u65F6\u957F\uFF08\u6BEB\u79D2\uFF0C\u9ED8\u8BA4 15000\uFF09")
3789
4252
  }),
3790
4253
  handler: async (p, ctx) => {
3791
4254
  const startTime = Date.now();
@@ -3840,7 +4303,7 @@ var networkCommand = registerCommand({
3840
4303
  };
3841
4304
  if (p.listen) {
3842
4305
  const page2 = ctx.page;
3843
- if (!page2) return fail7("No active page. Use --cdp to connect first.");
4306
+ if (!page2) return fail9("No active page. Use --cdp to connect first.");
3844
4307
  const captures = [];
3845
4308
  const consoleMessages = [];
3846
4309
  const wsCaptures = [];
@@ -3867,7 +4330,7 @@ var networkCommand = registerCommand({
3867
4330
  page2.off("response", handler);
3868
4331
  const duration = Date.now() - startTime;
3869
4332
  if (p.ws && wsCaptures.length > 0) {
3870
- return ok19({
4333
+ return ok22({
3871
4334
  url: "[listen mode]",
3872
4335
  duration,
3873
4336
  total: captures.length,
@@ -3881,9 +4344,9 @@ var networkCommand = registerCommand({
3881
4344
  });
3882
4345
  }
3883
4346
  if (p.format === "json") {
3884
- return ok19(buildJsonOutput("[listen mode]", captures, consoleMessages, captures.length, wsCaptures));
4347
+ return ok22(buildJsonOutput("[listen mode]", captures, consoleMessages, captures.length, wsCaptures));
3885
4348
  }
3886
- return ok19(buildSummaryOutput("[listen mode]", duration, captures, consoleMessages, captures.length, wsCaptures));
4349
+ return ok22(buildSummaryOutput("[listen mode]", duration, captures, consoleMessages, captures.length, wsCaptures));
3887
4350
  }
3888
4351
  const { context, page } = await createEphemeralContext(resolveLaunchOpts(ctx));
3889
4352
  try {
@@ -3938,7 +4401,7 @@ var networkCommand = registerCommand({
3938
4401
  }
3939
4402
  const duration = Date.now() - startTime;
3940
4403
  if (p.ws && wsCaptures.length > 0) {
3941
- return ok19({
4404
+ return ok22({
3942
4405
  url: p.url,
3943
4406
  duration,
3944
4407
  total: captures.length,
@@ -3952,9 +4415,9 @@ var networkCommand = registerCommand({
3952
4415
  });
3953
4416
  }
3954
4417
  if (p.format === "json") {
3955
- return ok19(buildJsonOutput(p.url, results, consoleMessages, totalCount, wsCaptures, searchResults));
4418
+ return ok22(buildJsonOutput(p.url, results, consoleMessages, totalCount, wsCaptures, searchResults));
3956
4419
  }
3957
- return ok19(buildSummaryOutput(p.url, duration, results, consoleMessages, totalCount, wsCaptures));
4420
+ return ok22(buildSummaryOutput(p.url, duration, results, consoleMessages, totalCount, wsCaptures));
3958
4421
  } finally {
3959
4422
  await closeEphemeralContext(context);
3960
4423
  }
@@ -3962,7 +4425,7 @@ var networkCommand = registerCommand({
3962
4425
  });
3963
4426
 
3964
4427
  // src/commands/ai-search-engines.ts
3965
- import { z as z20 } from "zod";
4428
+ import { z as z23 } from "zod";
3966
4429
  var ENGINE_CONFIGS = {
3967
4430
  deepseek: {
3968
4431
  key: "deepseek",
@@ -4216,7 +4679,7 @@ var ENGINE_CONFIGS = {
4216
4679
  }
4217
4680
  };
4218
4681
  var ALL_ENGINE_KEYS = Object.keys(ENGINE_CONFIGS);
4219
- var ENGINE_KEY_ENUM = z20.enum(ALL_ENGINE_KEYS);
4682
+ var ENGINE_KEY_ENUM = z23.enum(ALL_ENGINE_KEYS);
4220
4683
  function getEngineConfig(key) {
4221
4684
  return ENGINE_CONFIGS[key];
4222
4685
  }
@@ -4496,8 +4959,8 @@ function parseMarkdownResults(rawText) {
4496
4959
  }
4497
4960
 
4498
4961
  // src/commands/snapshot.ts
4499
- import { z as z21 } from "zod";
4500
- import { ok as ok20, fail as fail8, normalizeTips as normalizeTips3 } from "@dyyz1993/xcli-core";
4962
+ import { z as z24 } from "zod";
4963
+ import { ok as ok23, fail as fail10, normalizeTips as normalizeTips3 } from "@dyyz1993/xcli-core";
4501
4964
 
4502
4965
  // src/runtime/ref-store.ts
4503
4966
  var sessions = /* @__PURE__ */ new Map();
@@ -4738,8 +5201,8 @@ async function resolveRefParams(page, params, selectorKeys, cache, sessionId) {
4738
5201
  }
4739
5202
 
4740
5203
  // src/utils/site-semantics.ts
4741
- import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync, readFileSync } from "fs";
4742
- import { join as join4, dirname as dirname2 } from "path";
5204
+ import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
5205
+ import { join as join6, dirname as dirname2 } from "path";
4743
5206
  import { homedir as homedir4 } from "os";
4744
5207
  import { stringify, parse } from "yaml";
4745
5208
  import { execFile } from "child_process";
@@ -4817,10 +5280,10 @@ function inferAction(role, label) {
4817
5280
  return {};
4818
5281
  }
4819
5282
  function getSemanticsDir() {
4820
- return join4(homedir4(), ".xbrowser", "site-semantics");
5283
+ return join6(homedir4(), ".xbrowser", "site-semantics");
4821
5284
  }
4822
5285
  function getSemanticsPath(domain) {
4823
- return join4(getSemanticsDir(), `${domain}.yaml`);
5286
+ return join6(getSemanticsDir(), `${domain}.yaml`);
4824
5287
  }
4825
5288
  function extractDomain2(url) {
4826
5289
  try {
@@ -4834,9 +5297,9 @@ function saveSemantics(domain, pagePath, url, elements) {
4834
5297
  const filePath = getSemanticsPath(domain);
4835
5298
  const dir = dirname2(filePath);
4836
5299
  let site;
4837
- if (existsSync(filePath)) {
5300
+ if (existsSync3(filePath)) {
4838
5301
  try {
4839
- site = parse(readFileSync(filePath, "utf-8"));
5302
+ site = parse(readFileSync3(filePath, "utf-8"));
4840
5303
  } catch {
4841
5304
  site = { site: domain, pages: {} };
4842
5305
  }
@@ -4856,16 +5319,16 @@ function saveSemantics(domain, pagePath, url, elements) {
4856
5319
  }
4857
5320
  }
4858
5321
  site.updated_at = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
4859
- if (!existsSync(dir)) {
5322
+ if (!existsSync3(dir)) {
4860
5323
  mkdirSync3(dir, { recursive: true });
4861
5324
  }
4862
5325
  writeFileSync3(filePath, stringify(site, { lineWidth: 0 }));
4863
5326
  }
4864
5327
  function loadSemantics(domain) {
4865
5328
  const filePath = getSemanticsPath(domain);
4866
- if (!existsSync(filePath)) return null;
5329
+ if (!existsSync3(filePath)) return null;
4867
5330
  try {
4868
- return parse(readFileSync(filePath, "utf-8"));
5331
+ return parse(readFileSync3(filePath, "utf-8"));
4869
5332
  } catch {
4870
5333
  return null;
4871
5334
  }
@@ -5409,24 +5872,24 @@ var snapshotCommand = registerCommand({
5409
5872
  description: "Capture a quick page state snapshot \u2014 aria tree, visible text, or DOM summary",
5410
5873
  scope: "page",
5411
5874
  selectorParams: ["selector"],
5412
- parameters: z21.object({
5413
- type: z21.enum(["aria", "text", "dom", "all"]).default("aria").describe("Snapshot type: aria (accessibility tree), text (visible text), dom (element summary), all (combined)"),
5414
- selector: z21.string().optional().describe("Scope to a specific element"),
5415
- depth: z21.number().optional().default(6).describe("Max depth for DOM/aria tree"),
5416
- interactive: z21.boolean().optional().default(false).describe("Return interactive agent refs only"),
5417
- interactiveOnly: z21.boolean().optional().default(false).describe("Alias for interactive"),
5418
- i: z21.boolean().optional().default(false).describe("Short alias for interactive"),
5419
- compact: z21.boolean().optional().default(false).describe("Include compact xbrowser style snapshot text"),
5420
- c: z21.boolean().optional().default(false).describe("Short alias for compact"),
5421
- selectors: z21.boolean().optional().default(false).describe("Include ref to CSS selector map"),
5422
- all: z21.boolean().optional().default(false).describe("Include hidden interactive targets when using interactive snapshot")
5875
+ parameters: z24.object({
5876
+ type: z24.enum(["aria", "text", "dom", "all"]).default("aria").describe("Snapshot type: aria (accessibility tree), text (visible text), dom (element summary), all (combined)"),
5877
+ selector: z24.string().optional().describe("Scope to a specific element"),
5878
+ depth: z24.number().optional().default(6).describe("Max depth for DOM/aria tree"),
5879
+ interactive: z24.boolean().optional().default(false).describe("Return interactive agent refs only"),
5880
+ interactiveOnly: z24.boolean().optional().default(false).describe("Alias for interactive"),
5881
+ i: z24.boolean().optional().default(false).describe("Short alias for interactive"),
5882
+ compact: z24.boolean().optional().default(false).describe("Include compact xbrowser style snapshot text"),
5883
+ c: z24.boolean().optional().default(false).describe("Short alias for compact"),
5884
+ selectors: z24.boolean().optional().default(false).describe("Include ref to CSS selector map"),
5885
+ all: z24.boolean().optional().default(false).describe("Include hidden interactive targets when using interactive snapshot")
5423
5886
  }),
5424
- result: z21.object({
5425
- url: z21.string(),
5426
- title: z21.string(),
5427
- aria: z21.string().optional(),
5428
- text: z21.string().optional(),
5429
- dom: z21.record(z21.unknown()).optional()
5887
+ result: z24.object({
5888
+ url: z24.string(),
5889
+ title: z24.string(),
5890
+ aria: z24.string().optional(),
5891
+ text: z24.string().optional(),
5892
+ dom: z24.record(z24.unknown()).optional()
5430
5893
  }),
5431
5894
  handler: async (p, ctx) => {
5432
5895
  const page = ctx.page;
@@ -5440,7 +5903,7 @@ var snapshotCommand = registerCommand({
5440
5903
  if (p.compact || p.c || p.interactive || p.interactiveOnly || p.i) {
5441
5904
  observation.compact = formatObservationCompact(observation, { selectors: p.selectors });
5442
5905
  }
5443
- return ok20(observation, normalizeTips3([
5906
+ return ok23(observation, normalizeTips3([
5444
5907
  `refs refreshed for ${observation.targets.length} targets; use click @e1 or fill @e2 "text"`
5445
5908
  ]));
5446
5909
  }
@@ -5448,15 +5911,15 @@ var snapshotCommand = registerCommand({
5448
5911
  const aria = await captureAriaSnapshot(page, p.selector, p.depth);
5449
5912
  const tips = await buildRefTips(page, aria);
5450
5913
  persistSemantics(url, aria);
5451
- return ok20({ url, title, aria }, normalizeTips3(tips));
5914
+ return ok23({ url, title, aria }, normalizeTips3(tips));
5452
5915
  }
5453
5916
  if (p.type === "text") {
5454
5917
  const text = await captureTextSnapshot(page, p.selector);
5455
- return ok20({ url, title, text });
5918
+ return ok23({ url, title, text });
5456
5919
  }
5457
5920
  if (p.type === "dom") {
5458
5921
  const dom = await captureDomSnapshot(page, p.selector, p.depth ?? 6);
5459
- return ok20({ url, title, dom });
5922
+ return ok23({ url, title, dom });
5460
5923
  }
5461
5924
  if (p.type === "all") {
5462
5925
  const [aria, text, dom] = await Promise.all([
@@ -5466,9 +5929,9 @@ var snapshotCommand = registerCommand({
5466
5929
  ]);
5467
5930
  const tips = await buildRefTips(page, aria);
5468
5931
  persistSemantics(url, aria);
5469
- return ok20({ url, title, aria, text, dom }, normalizeTips3(tips));
5932
+ return ok23({ url, title, aria, text, dom }, normalizeTips3(tips));
5470
5933
  }
5471
- return fail8(`Unknown snapshot type: ${p.type}`);
5934
+ return fail10(`Unknown snapshot type: ${p.type}`);
5472
5935
  }
5473
5936
  });
5474
5937
  function persistSemantics(url, aria) {
@@ -5545,22 +6008,22 @@ async function captureDomSnapshot(page, selector, maxDepth) {
5545
6008
  }
5546
6009
 
5547
6010
  // src/commands/agent.ts
5548
- import { z as z22 } from "zod";
5549
- import { ok as ok21, normalizeTips as normalizeTips4 } from "@dyyz1993/xcli-core";
6011
+ import { z as z25 } from "zod";
6012
+ import { ok as ok24, normalizeTips as normalizeTips4 } from "@dyyz1993/xcli-core";
5550
6013
  var observeCommand = registerCommand({
5551
6014
  name: "observe",
5552
6015
  description: "Observe the current page as structured agent targets with session refs",
5553
6016
  scope: "page",
5554
- parameters: z22.object({
5555
- includeHidden: z22.boolean().optional().default(false).describe("Include hidden elements in the target list"),
5556
- limit: z22.number().int().positive().max(300).optional().default(80).describe("Maximum number of targets to return"),
5557
- compact: z22.boolean().optional().default(false).describe("Include compact xbrowser style snapshot text"),
5558
- selectors: z22.boolean().optional().default(false).describe("Include ref to stable CSS selector map")
6017
+ parameters: z25.object({
6018
+ includeHidden: z25.boolean().optional().default(false).describe("Include hidden elements in the target list"),
6019
+ limit: z25.number().int().positive().max(300).optional().default(80).describe("Maximum number of targets to return"),
6020
+ compact: z25.boolean().optional().default(false).describe("Include compact xbrowser style snapshot text"),
6021
+ selectors: z25.boolean().optional().default(false).describe("Include ref to stable CSS selector map")
5559
6022
  }),
5560
- result: z22.object({
5561
- targets: z22.array(z22.record(z22.unknown())),
5562
- selectors: z22.record(z22.unknown()).optional(),
5563
- compact: z22.string().optional()
6023
+ result: z25.object({
6024
+ targets: z25.array(z25.record(z25.unknown())),
6025
+ selectors: z25.record(z25.unknown()).optional(),
6026
+ compact: z25.string().optional()
5564
6027
  }).passthrough(),
5565
6028
  handler: async (p, ctx) => {
5566
6029
  const observation = await observePage(ctx.page, ctx.sessionId, {
@@ -5569,7 +6032,7 @@ var observeCommand = registerCommand({
5569
6032
  });
5570
6033
  if (p.selectors) observation.selectors = buildSelectorMap(observation);
5571
6034
  if (p.compact) observation.compact = formatObservationCompact(observation, { selectors: p.selectors });
5572
- return ok21(observation, normalizeTips4([
6035
+ return ok24(observation, normalizeTips4([
5573
6036
  `refs refreshed for ${observation.targets.length} targets; use act --ref @e1 --action click or click @e1`
5574
6037
  ]));
5575
6038
  }
@@ -5579,14 +6042,14 @@ var actCommand = registerCommand({
5579
6042
  description: "Perform an agent action using an observe ref or explicit selector",
5580
6043
  scope: "element",
5581
6044
  selectorParams: ["selector"],
5582
- parameters: z22.object({
5583
- action: z22.enum(["click", "fill", "type", "press", "select", "check", "hover"]).default("click"),
5584
- ref: z22.string().optional().describe("Session-scoped ref returned by observe, such as e1"),
5585
- selector: z22.string().optional().describe("CSS selector fallback when no ref is available"),
5586
- value: z22.string().optional().describe("Value for fill/type/select"),
5587
- key: z22.string().optional().describe("Key for press"),
5588
- force: z22.boolean().optional().default(false).describe("Bypass actionability checks"),
5589
- timeout: z22.number().optional().default(1e4).describe("Playwright action timeout in milliseconds")
6045
+ parameters: z25.object({
6046
+ action: z25.enum(["click", "fill", "type", "press", "select", "check", "hover"]).default("click"),
6047
+ ref: z25.string().optional().describe("Session-scoped ref returned by observe, such as e1"),
6048
+ selector: z25.string().optional().describe("CSS selector fallback when no ref is available"),
6049
+ value: z25.string().optional().describe("Value for fill/type/select"),
6050
+ key: z25.string().optional().describe("Key for press"),
6051
+ force: z25.boolean().optional().default(false).describe("Bypass actionability checks"),
6052
+ timeout: z25.number().optional().default(1e4).describe("Playwright action timeout in milliseconds")
5590
6053
  }).refine((p) => !!p.ref || !!p.selector, {
5591
6054
  message: "Either ref or selector is required"
5592
6055
  }),
@@ -5600,7 +6063,7 @@ var actCommand = registerCommand({
5600
6063
  tips: normalizeTips4(result.stale ? ["run observe again to refresh refs"] : [])
5601
6064
  };
5602
6065
  }
5603
- return ok21(result, normalizeTips4(result.stale ? ["ref screen hash changed; run observe if the next action is uncertain"] : []));
6066
+ return ok24(result, normalizeTips4(result.stale ? ["ref screen hash changed; run observe if the next action is uncertain"] : []));
5604
6067
  }
5605
6068
  });
5606
6069
  var waitForCommand = registerCommand({
@@ -5608,16 +6071,16 @@ var waitForCommand = registerCommand({
5608
6071
  description: "Wait for agent predicates such as text, URL, load state, selector state, or screen hash changes",
5609
6072
  scope: "page",
5610
6073
  selectorParams: ["selector"],
5611
- parameters: z22.object({
5612
- selector: z22.string().optional().describe("CSS selector or observe ref to wait for"),
5613
- state: z22.enum(["attached", "detached", "visible", "hidden"]).optional().default("visible"),
5614
- text: z22.string().optional().describe("Visible text to wait for"),
5615
- url: z22.string().optional().describe("URL substring or glob pattern to wait for"),
5616
- load: z22.enum(["load", "domcontentloaded", "networkidle"]).optional().describe("Load state to wait for"),
5617
- fn: z22.string().optional().describe("JavaScript predicate to wait for"),
5618
- screenHashChanged: z22.string().optional().describe("Previous screenHash from observe"),
5619
- timeout: z22.number().optional().default(3e4),
5620
- pollInterval: z22.number().optional().default(200)
6074
+ parameters: z25.object({
6075
+ selector: z25.string().optional().describe("CSS selector or observe ref to wait for"),
6076
+ state: z25.enum(["attached", "detached", "visible", "hidden"]).optional().default("visible"),
6077
+ text: z25.string().optional().describe("Visible text to wait for"),
6078
+ url: z25.string().optional().describe("URL substring or glob pattern to wait for"),
6079
+ load: z25.enum(["load", "domcontentloaded", "networkidle"]).optional().describe("Load state to wait for"),
6080
+ fn: z25.string().optional().describe("JavaScript predicate to wait for"),
6081
+ screenHashChanged: z25.string().optional().describe("Previous screenHash from observe"),
6082
+ timeout: z25.number().optional().default(3e4),
6083
+ pollInterval: z25.number().optional().default(200)
5621
6084
  }).refine((p) => [p.selector, p.text, p.url, p.load, p.fn, p.screenHashChanged].filter(Boolean).length === 1, {
5622
6085
  message: "Provide exactly one wait predicate: selector, text, url, load, fn, or screenHashChanged"
5623
6086
  }),
@@ -5631,30 +6094,30 @@ var waitForCommand = registerCommand({
5631
6094
  tips: []
5632
6095
  };
5633
6096
  }
5634
- return ok21(result);
6097
+ return ok24(result);
5635
6098
  }
5636
6099
  });
5637
6100
 
5638
6101
  // src/commands/tab.ts
5639
- import { z as z23 } from "zod";
5640
- import { ok as ok22, fail as fail9 } from "@dyyz1993/xcli-core";
5641
- var TabParams = z23.object({
5642
- subcommand: z23.enum(["list", "new", "close", "switch"]),
5643
- url: z23.string().optional(),
5644
- index: z23.number().int().min(0).optional()
6102
+ import { z as z26 } from "zod";
6103
+ import { ok as ok25, fail as fail11 } from "@dyyz1993/xcli-core";
6104
+ var TabParams = z26.object({
6105
+ subcommand: z26.enum(["list", "new", "close", "switch"]),
6106
+ url: z26.string().optional(),
6107
+ index: z26.number().int().min(0).optional()
5645
6108
  });
5646
6109
  var tabCommand = registerCommand({
5647
6110
  name: "tab",
5648
6111
  description: "Manage browser tabs: list, new, close, switch",
5649
6112
  scope: "page",
5650
6113
  parameters: TabParams,
5651
- result: z23.object({
5652
- success: z23.boolean(),
5653
- data: z23.unknown()
6114
+ result: z26.object({
6115
+ success: z26.boolean(),
6116
+ data: z26.unknown()
5654
6117
  }),
5655
6118
  handler: async (p, ctx) => {
5656
6119
  if (!ctx.browserContext) {
5657
- return fail9("No browser context available. Use --cdp to connect to a browser first.");
6120
+ return fail11("No browser context available. Use --cdp to connect to a browser first.");
5658
6121
  }
5659
6122
  const pages = ctx.browserContext.pages();
5660
6123
  switch (p.subcommand) {
@@ -5667,7 +6130,7 @@ var tabCommand = registerCommand({
5667
6130
  case "switch":
5668
6131
  return handleSwitch(p, pages, ctx);
5669
6132
  default:
5670
- return fail9(`Unknown subcommand: ${p.subcommand}`);
6133
+ return fail11(`Unknown subcommand: ${p.subcommand}`);
5671
6134
  }
5672
6135
  }
5673
6136
  });
@@ -5682,7 +6145,7 @@ async function handleList(pages, ctx) {
5682
6145
  if (isActive) activeIndex = i;
5683
6146
  tabs.push({ index: i, url, title, active: isActive });
5684
6147
  }
5685
- return ok22({ tabs, total: tabs.length, activeIndex });
6148
+ return ok25({ tabs, total: tabs.length, activeIndex });
5686
6149
  }
5687
6150
  async function handleNew(p, _pages, ctx) {
5688
6151
  const newPage = await ctx.browserContext.newPage();
@@ -5710,7 +6173,7 @@ async function handleNew(p, _pages, ctx) {
5710
6173
  const title = await newPage.title().catch(() => "");
5711
6174
  const allPages = ctx.browserContext.pages();
5712
6175
  const newIndex = allPages.indexOf(newPage);
5713
- return ok22({
6176
+ return ok25({
5714
6177
  index: newIndex >= 0 ? newIndex : allPages.length - 1,
5715
6178
  url: newPage.url(),
5716
6179
  title,
@@ -5721,11 +6184,11 @@ async function handleNew(p, _pages, ctx) {
5721
6184
  async function handleClose(p, ctx) {
5722
6185
  const currentPages = ctx.browserContext.pages();
5723
6186
  if (currentPages.length <= 1) {
5724
- return fail9("Cannot close the last remaining tab");
6187
+ return fail11("Cannot close the last remaining tab");
5725
6188
  }
5726
6189
  const closeIndex = p.index ?? currentPages.findIndex((pg) => pg === ctx.page);
5727
6190
  if (closeIndex < 0 || closeIndex >= currentPages.length) {
5728
- return fail9(`Invalid tab index: ${closeIndex}. Valid range: 0-${currentPages.length - 1}`);
6191
+ return fail11(`Invalid tab index: ${closeIndex}. Valid range: 0-${currentPages.length - 1}`);
5729
6192
  }
5730
6193
  const pageToClose = currentPages[closeIndex];
5731
6194
  const isActivePage = pageToClose === ctx.page;
@@ -5740,7 +6203,7 @@ async function handleClose(p, ctx) {
5740
6203
  }
5741
6204
  ctx.page = newActivePage;
5742
6205
  }
5743
- return ok22({
6206
+ return ok25({
5744
6207
  closedIndex: closeIndex,
5745
6208
  total: remainingPages.length,
5746
6209
  activeIndex: isActivePage ? closeIndex < remainingPages.length ? closeIndex : remainingPages.length - 1 : remainingPages.findIndex((pg) => pg === ctx.page)
@@ -5748,21 +6211,32 @@ async function handleClose(p, ctx) {
5748
6211
  }
5749
6212
  async function handleSwitch(p, pages, ctx) {
5750
6213
  if (p.index === void 0) {
5751
- return fail9("Parameter --index is required for switch subcommand");
6214
+ return fail11("Parameter --index is required for switch subcommand");
5752
6215
  }
5753
6216
  if (p.index < 0 || p.index >= pages.length) {
5754
- return fail9(`Invalid tab index: ${p.index}. Valid range: 0-${pages.length - 1}`);
6217
+ return fail11(`Invalid tab index: ${p.index}. Valid range: 0-${pages.length - 1}`);
5755
6218
  }
5756
6219
  const targetPage = pages[p.index];
5757
6220
  await targetPage.bringToFront().catch(() => {
5758
6221
  });
6222
+ for (let i = 0; i < 5; i++) {
6223
+ const okProbe = await targetPage.evaluate("1").then(() => true, () => false);
6224
+ if (okProbe) break;
6225
+ await new Promise((r) => setTimeout(r, 300));
6226
+ }
6227
+ try {
6228
+ const mouse = targetPage.mouse;
6229
+ if (mouse) await mouse.move(1, 1).catch(() => {
6230
+ });
6231
+ } catch {
6232
+ }
5759
6233
  const session = ctx.sessionId ? getSessionById(ctx.sessionId) : void 0;
5760
6234
  if (session) {
5761
6235
  setActivePage(session, targetPage);
5762
6236
  }
5763
6237
  ctx.page = targetPage;
5764
6238
  const title = await targetPage.title().catch(() => "");
5765
- return ok22({
6239
+ return ok25({
5766
6240
  index: p.index,
5767
6241
  url: targetPage.url(),
5768
6242
  title,
@@ -5771,9 +6245,9 @@ async function handleSwitch(p, pages, ctx) {
5771
6245
  }
5772
6246
 
5773
6247
  // src/commands/addinitscript.ts
5774
- import { z as z24 } from "zod";
5775
- import { ok as ok23 } from "@dyyz1993/xcli-core";
5776
- import { readFileSync as readFileSync2 } from "fs";
6248
+ import { z as z27 } from "zod";
6249
+ import { ok as ok26 } from "@dyyz1993/xcli-core";
6250
+ import { readFileSync as readFileSync4 } from "fs";
5777
6251
 
5778
6252
  // src/chain-parser.ts
5779
6253
  import { splitCommand, parseCommandArgs } from "@dyyz1993/xcli-core";
@@ -5913,14 +6387,14 @@ registerCommandDefinition("tab", ["subcommand"]);
5913
6387
  registerCommandDefinition("mouse", ["action", "x", "y"]);
5914
6388
 
5915
6389
  // src/commands/addinitscript.ts
5916
- var InitScriptParams = z24.object({
5917
- script: z24.string().optional(),
5918
- file: z24.string().optional(),
5919
- stdin: z24.boolean().optional(),
5920
- name: z24.string().optional(),
5921
- list: z24.boolean().optional(),
5922
- remove: z24.union([z24.string(), z24.boolean()]).optional(),
5923
- base64: z24.string().optional()
6390
+ var InitScriptParams = z27.object({
6391
+ script: z27.string().optional(),
6392
+ file: z27.string().optional(),
6393
+ stdin: z27.boolean().optional(),
6394
+ name: z27.string().optional(),
6395
+ list: z27.boolean().optional(),
6396
+ remove: z27.union([z27.string(), z27.boolean()]).optional(),
6397
+ base64: z27.string().optional()
5924
6398
  });
5925
6399
  var registeredScripts = /* @__PURE__ */ new Map();
5926
6400
  function resolveScriptContent(params) {
@@ -5928,7 +6402,7 @@ function resolveScriptContent(params) {
5928
6402
  return Buffer.from(params.base64, "base64").toString("utf-8");
5929
6403
  }
5930
6404
  if (params.file) {
5931
- return readFileSync2(params.file, "utf-8");
6405
+ return readFileSync4(params.file, "utf-8");
5932
6406
  }
5933
6407
  if (params.stdin) {
5934
6408
  return null;
@@ -5954,14 +6428,14 @@ var addInitScriptCommand = registerCommand({
5954
6428
  description: "Add an initialization script that runs on every page load",
5955
6429
  scope: "page",
5956
6430
  parameters: InitScriptParams,
5957
- result: z24.object({
5958
- scripts: z24.array(z24.object({ name: z24.string(), size: z24.number(), preview: z24.string() })).optional(),
5959
- removed: z24.string().optional(),
5960
- existed: z24.boolean().optional(),
5961
- error: z24.string().optional(),
5962
- registered: z24.string().optional(),
5963
- hint: z24.string().optional(),
5964
- executedImmediately: z24.boolean().optional()
6431
+ result: z27.object({
6432
+ scripts: z27.array(z27.object({ name: z27.string(), size: z27.number(), preview: z27.string() })).optional(),
6433
+ removed: z27.string().optional(),
6434
+ existed: z27.boolean().optional(),
6435
+ error: z27.string().optional(),
6436
+ registered: z27.string().optional(),
6437
+ hint: z27.string().optional(),
6438
+ executedImmediately: z27.boolean().optional()
5965
6439
  }).passthrough(),
5966
6440
  handler: async (params, ctx) => {
5967
6441
  if (params.list) {
@@ -5970,20 +6444,20 @@ var addInitScriptCommand = registerCommand({
5970
6444
  size: content2.length,
5971
6445
  preview: content2.slice(0, 80)
5972
6446
  }));
5973
- return ok23({ scripts });
6447
+ return ok26({ scripts });
5974
6448
  }
5975
6449
  if (params.remove && typeof params.remove === "string") {
5976
6450
  const existed = registeredScripts.delete(params.remove);
5977
- return ok23({ removed: params.remove, existed });
6451
+ return ok26({ removed: params.remove, existed });
5978
6452
  }
5979
6453
  const removeTarget = (typeof params.remove === "string" ? params.remove : null) || (params.name && !resolveScriptContent(params) ? params.name : null);
5980
6454
  if (removeTarget && !resolveScriptContent(params)) {
5981
6455
  const existed = registeredScripts.delete(removeTarget);
5982
- return ok23({ removed: removeTarget, existed });
6456
+ return ok26({ removed: removeTarget, existed });
5983
6457
  }
5984
6458
  let content = params.stdin ? await readStdin() : resolveScriptContent(params);
5985
6459
  if (!content) {
5986
- return ok23({ error: "No script content provided. Use --script, --file, --stdin, or --base64" });
6460
+ return ok26({ error: "No script content provided. Use --script, --file, --stdin, or --base64" });
5987
6461
  }
5988
6462
  const scriptName = params.name ?? `script-${Date.now()}`;
5989
6463
  registeredScripts.set(scriptName, content);
@@ -5991,45 +6465,45 @@ var addInitScriptCommand = registerCommand({
5991
6465
  try {
5992
6466
  await ctx.page.evaluate(content);
5993
6467
  } catch {
5994
- return ok23({
6468
+ return ok26({
5995
6469
  registered: scriptName,
5996
6470
  hint: "Script registered for future page loads; immediate execution skipped (page may not be ready)"
5997
6471
  });
5998
6472
  }
5999
- return ok23({ registered: scriptName, executedImmediately: true });
6473
+ return ok26({ registered: scriptName, executedImmediately: true });
6000
6474
  }
6001
6475
  });
6002
6476
  registerCommandDefinition("addinitscript", ["script"]);
6003
6477
 
6004
6478
  // src/commands/find.ts
6005
- import { z as z25 } from "zod";
6006
- import { ok as ok24, fail as fail10, normalizeTips as normalizeTips5 } from "@dyyz1993/xcli-core";
6007
- var actionSchema2 = z25.enum(["click", "fill", "type", "select", "hover", "check"]);
6479
+ import { z as z28 } from "zod";
6480
+ import { ok as ok27, fail as fail12, normalizeTips as normalizeTips5 } from "@dyyz1993/xcli-core";
6481
+ var actionSchema2 = z28.enum(["click", "fill", "type", "select", "hover", "check"]);
6008
6482
  var findCommand = registerCommand({
6009
6483
  name: "find",
6010
6484
  description: "Find elements by semantic strategy (text/role/label/placeholder/testid) and optionally perform an action",
6011
6485
  scope: "page",
6012
- parameters: z25.object({
6013
- strategy: z25.enum(["text", "role", "label", "placeholder", "testid", "alt", "title", "first", "last", "nth"]),
6014
- value: z25.string(),
6015
- name: z25.string().optional(),
6016
- exact: z25.boolean().optional().default(false),
6017
- operation: z25.string().optional().describe('Trailing operation syntax, e.g. click, fill "text", type "text"'),
6486
+ parameters: z28.object({
6487
+ strategy: z28.enum(["text", "role", "label", "placeholder", "testid", "alt", "title", "first", "last", "nth"]),
6488
+ value: z28.string(),
6489
+ name: z28.string().optional(),
6490
+ exact: z28.boolean().optional().default(false),
6491
+ operation: z28.string().optional().describe('Trailing operation syntax, e.g. click, fill "text", type "text"'),
6018
6492
  action: actionSchema2.optional().describe("Action to perform when not using trailing operation syntax"),
6019
- actionValue: z25.string().optional().describe("Value for fill/type/select when using action"),
6020
- index: z25.number().int().optional().describe("Index for nth strategy"),
6021
- click: z25.boolean().optional().default(false),
6022
- fill: z25.string().optional(),
6023
- type: z25.string().optional(),
6024
- select: z25.string().optional(),
6025
- hover: z25.boolean().optional().default(false),
6026
- check: z25.boolean().optional().default(false),
6027
- timeout: z25.number().optional().default(1e4)
6493
+ actionValue: z28.string().optional().describe("Value for fill/type/select when using action"),
6494
+ index: z28.number().int().optional().describe("Index for nth strategy"),
6495
+ click: z28.boolean().optional().default(false),
6496
+ fill: z28.string().optional(),
6497
+ type: z28.string().optional(),
6498
+ select: z28.string().optional(),
6499
+ hover: z28.boolean().optional().default(false),
6500
+ check: z28.boolean().optional().default(false),
6501
+ timeout: z28.number().optional().default(1e4)
6028
6502
  }),
6029
- result: z25.object({
6030
- matched: z25.number(),
6031
- selector: z25.string(),
6032
- action: z25.string().optional()
6503
+ result: z28.object({
6504
+ matched: z28.number(),
6505
+ selector: z28.string(),
6506
+ action: z28.string().optional()
6033
6507
  }),
6034
6508
  handler: async (p, ctx) => {
6035
6509
  const page = ctx.page;
@@ -6044,7 +6518,7 @@ var findCommand = registerCommand({
6044
6518
  });
6045
6519
  const count = await locator.count();
6046
6520
  if (count === 0) {
6047
- return fail10(`No element found with ${p.strategy}="${p.value}"`);
6521
+ return fail12(`No element found with ${p.strategy}="${p.value}"`);
6048
6522
  }
6049
6523
  const tips = [];
6050
6524
  const target = selectTarget(locator, p.strategy);
@@ -6056,15 +6530,15 @@ var findCommand = registerCommand({
6056
6530
  await target.click({ timeout: p.timeout, force: true });
6057
6531
  return okWithTips({ matched: count, selector, action: "click" }, tips);
6058
6532
  } else if (actionName === "fill") {
6059
- if (actionValue === void 0) return fail10("find fill requires a value");
6533
+ if (actionValue === void 0) return fail12("find fill requires a value");
6060
6534
  await target.fill(actionValue, { timeout: p.timeout, force: true });
6061
6535
  return okWithTips({ matched: count, selector, action: `fill("${actionValue}")` }, tips);
6062
6536
  } else if (actionName === "type") {
6063
- if (actionValue === void 0) return fail10("find type requires a value");
6537
+ if (actionValue === void 0) return fail12("find type requires a value");
6064
6538
  await target.type(actionValue, { delay: 10, timeout: p.timeout });
6065
6539
  return okWithTips({ matched: count, selector, action: `type("${actionValue}")` }, tips);
6066
6540
  } else if (actionName === "select") {
6067
- if (actionValue === void 0) return fail10("find select requires a value");
6541
+ if (actionValue === void 0) return fail12("find select requires a value");
6068
6542
  await target.selectOption(actionValue);
6069
6543
  return okWithTips({ matched: count, selector, action: `select("${actionValue}")` }, tips);
6070
6544
  } else if (actionName === "hover") {
@@ -6078,7 +6552,7 @@ var findCommand = registerCommand({
6078
6552
  }
6079
6553
  });
6080
6554
  function okWithTips(data, tips) {
6081
- const result = ok24(data);
6555
+ const result = ok27(data);
6082
6556
  if (tips.length > 0) result.tips = normalizeTips5(tips);
6083
6557
  return result;
6084
6558
  }
@@ -6202,7 +6676,7 @@ function attachWaitForHuman(ctx, getOrCreateWSServer) {
6202
6676
  if (!ctx.page) {
6203
6677
  throw new Error("waitForHuman requires an active page");
6204
6678
  }
6205
- const { HumanInteractionManager: HumanInteractionManager2 } = await import("./human-interaction-C5OEGXGO.js");
6679
+ const { HumanInteractionManager: HumanInteractionManager2 } = await import("./human-interaction-Y5IDH6LD.js");
6206
6680
  const wsServer2 = await getOrCreateWSServer(ctx.browserContext);
6207
6681
  const manager = new HumanInteractionManager2(wsServer2, ctx.page);
6208
6682
  return manager.waitForHuman(options);
@@ -6210,7 +6684,7 @@ function attachWaitForHuman(ctx, getOrCreateWSServer) {
6210
6684
  }
6211
6685
  function attachDetectAntiBot(ctx) {
6212
6686
  ctx.detectAntiBot = async (page, config) => {
6213
- const { detectAntiBot: detectAntiBot2 } = await import("./anti-bot-DR56Y63V.js");
6687
+ const { detectAntiBot: detectAntiBot2 } = await import("./anti-bot-GTTYNEFB.js");
6214
6688
  return detectAntiBot2(page, config);
6215
6689
  };
6216
6690
  }
@@ -6226,18 +6700,18 @@ import {
6226
6700
  Core
6227
6701
  } from "@dyyz1993/xcli-core";
6228
6702
  import { resolve as resolve2 } from "path";
6229
- import { existsSync as existsSync4, readdirSync } from "fs";
6703
+ import { existsSync as existsSync6, readdirSync } from "fs";
6230
6704
  import { homedir as homedir5 } from "os";
6231
6705
 
6232
6706
  // src/plugin/metadata-parser.ts
6233
- import { existsSync as existsSync2 } from "fs";
6707
+ import { existsSync as existsSync4 } from "fs";
6234
6708
  import { resolve } from "path";
6235
6709
 
6236
6710
  // src/utils/json-file.ts
6237
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync4 } from "fs";
6711
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
6238
6712
  function readJsonFile(filePath, defaultValue) {
6239
6713
  try {
6240
- const content = readFileSync3(filePath, "utf-8");
6714
+ const content = readFileSync5(filePath, "utf-8");
6241
6715
  return JSON.parse(content);
6242
6716
  } catch {
6243
6717
  return defaultValue;
@@ -6249,7 +6723,7 @@ var PluginMetadataParser = class {
6249
6723
  static XBROWSER_KEYWORDS = ["xbrowser", "xbrowser-plugin"];
6250
6724
  static parseFromPackageJson(pluginPath) {
6251
6725
  const packageJsonPath = resolve(pluginPath, "package.json");
6252
- if (!existsSync2(packageJsonPath)) {
6726
+ if (!existsSync4(packageJsonPath)) {
6253
6727
  return null;
6254
6728
  }
6255
6729
  const packageJson = readJsonFile(packageJsonPath, null);
@@ -6313,20 +6787,20 @@ var PluginMetadataParser = class {
6313
6787
  };
6314
6788
 
6315
6789
  // src/plugin/ensure-deps.ts
6316
- import { existsSync as existsSync3, mkdirSync as mkdirSync4, writeFileSync as writeFileSync5 } from "fs";
6317
- import { join as join5 } from "path";
6790
+ import { existsSync as existsSync5, mkdirSync as mkdirSync4, writeFileSync as writeFileSync5 } from "fs";
6791
+ import { join as join7 } from "path";
6318
6792
  import { execSync } from "child_process";
6319
6793
  var SHARED_PLUGIN_DEPENDENCIES = {
6320
6794
  "zod": "^3.24.0",
6321
6795
  "@dyyz1993/xcli-core": "^0.12.1"
6322
6796
  };
6323
6797
  function ensurePluginDependencies(pluginsDir) {
6324
- const zodPath = join5(pluginsDir, "node_modules", "zod");
6325
- if (existsSync3(zodPath)) return;
6798
+ const zodPath = join7(pluginsDir, "node_modules", "zod");
6799
+ if (existsSync5(zodPath)) return;
6326
6800
  mkdirSync4(pluginsDir, { recursive: true });
6327
- const pkgPath = join5(pluginsDir, "package.json");
6801
+ const pkgPath = join7(pluginsDir, "package.json");
6328
6802
  let pkg = {};
6329
- if (existsSync3(pkgPath)) {
6803
+ if (existsSync5(pkgPath)) {
6330
6804
  try {
6331
6805
  pkg = readJsonFile(pkgPath, {});
6332
6806
  } catch {
@@ -6340,7 +6814,7 @@ function ensurePluginDependencies(pluginsDir) {
6340
6814
  needsInstall = true;
6341
6815
  }
6342
6816
  }
6343
- if (!needsInstall && existsSync3(join5(pluginsDir, "node_modules"))) return;
6817
+ if (!needsInstall && existsSync5(join7(pluginsDir, "node_modules"))) return;
6344
6818
  pkg.dependencies = existingDeps;
6345
6819
  pkg.private = true;
6346
6820
  pkg.description = pkg.description || "xbrowser plugins \u2014 shared dependencies";
@@ -6598,7 +7072,7 @@ var XBrowserPluginLoader = class {
6598
7072
  const loaded = [];
6599
7073
  const seen = /* @__PURE__ */ new Set();
6600
7074
  for (const dir of dirs) {
6601
- if (!existsSync4(dir)) continue;
7075
+ if (!existsSync6(dir)) continue;
6602
7076
  const entries = readdirSync(dir, { withFileTypes: true });
6603
7077
  for (const entry of entries) {
6604
7078
  if (!entry.isDirectory()) continue;
@@ -6606,12 +7080,12 @@ var XBrowserPluginLoader = class {
6606
7080
  seen.add(entry.name);
6607
7081
  const pluginDir = resolve2(dir, entry.name);
6608
7082
  let indexPath = resolve2(pluginDir, "index.js");
6609
- if (!existsSync4(indexPath)) {
7083
+ if (!existsSync6(indexPath)) {
6610
7084
  indexPath = resolve2(pluginDir, "index.ts");
6611
7085
  }
6612
- if (!existsSync4(indexPath)) continue;
7086
+ if (!existsSync6(indexPath)) continue;
6613
7087
  try {
6614
- if (!existsSync4(resolve2(pluginDir, "package.json"))) {
7088
+ if (!existsSync6(resolve2(pluginDir, "package.json"))) {
6615
7089
  console.warn(`\u26A0\uFE0F Plugin "${entry.name}" has no package.json. Use "xbrowser create ${entry.name} --template static" for proper structure.`);
6616
7090
  } else {
6617
7091
  const metadata = PluginMetadataParser.parseFromPackageJson(pluginDir);
@@ -7268,7 +7742,7 @@ async function loadHooks() {
7268
7742
 
7269
7743
  // src/executor.ts
7270
7744
  import { homedir as homedir6 } from "os";
7271
- import { join as join6 } from "path";
7745
+ import { join as join8 } from "path";
7272
7746
  function levenshtein(a, b) {
7273
7747
  const m = a.length, n = b.length;
7274
7748
  const dp = Array(m + 1).fill(null).map(() => Array(n + 1).fill(0));
@@ -7283,7 +7757,7 @@ function levenshtein(a, b) {
7283
7757
  }
7284
7758
  var NAVIGATION_COMMANDS = /* @__PURE__ */ new Set(["goto", "back", "forward", "refresh"]);
7285
7759
  var snapshotHintShown = /* @__PURE__ */ new WeakSet();
7286
- var CONFIG_DIR2 = join6(homedir6(), ".xbrowser");
7760
+ var CONFIG_DIR2 = join8(homedir6(), ".xbrowser");
7287
7761
  var storageCache = /* @__PURE__ */ new Map();
7288
7762
  function getPluginStorage(pluginName) {
7289
7763
  if (!storageCache.has(pluginName)) {
@@ -7295,7 +7769,7 @@ var archiveInitialized = false;
7295
7769
  function ensureArchiveInit() {
7296
7770
  if (!archiveInitialized) {
7297
7771
  try {
7298
- configureArchiveStore({ archiveDir: join6(homedir6(), ".xbrowser", "archives") });
7772
+ configureArchiveStore({ archiveDir: join8(homedir6(), ".xbrowser", "archives") });
7299
7773
  } catch {
7300
7774
  }
7301
7775
  archiveInitialized = true;
@@ -7319,7 +7793,7 @@ async function guardCheck(commandName) {
7319
7793
  }
7320
7794
  }
7321
7795
  function errorResult(message) {
7322
- return { ...fail11(message), duration: 0 };
7796
+ return { ...fail13(message), duration: 0 };
7323
7797
  }
7324
7798
  function tipsToMessages(tips) {
7325
7799
  if (!tips || tips.length === 0) return [];
@@ -7399,7 +7873,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
7399
7873
  }
7400
7874
  let targetPageOverride = null;
7401
7875
  if (_target && extraOpts?.cdpEndpoint) {
7402
- const { findTargetPage } = await import("./browser-HBL72GPZ.js");
7876
+ const { findTargetPage } = await import("./browser-3HQKDZQ6.js");
7403
7877
  targetPageOverride = await findTargetPage(extraOpts.cdpEndpoint, _target);
7404
7878
  if (!targetPageOverride) {
7405
7879
  return errorResult(`Target "${_target}" not found. Use 'xbrowser targets --cdp ${extraOpts.cdpEndpoint}' to list available pages.`);
@@ -7416,8 +7890,15 @@ async function executeCommand(commandName, params, sessionName = "default", extr
7416
7890
  params = result.data;
7417
7891
  }
7418
7892
  if (command.scope !== "cli" && !process.env.XBROWSER_DAEMON_WORKER) {
7419
- const { forwardExec } = await import("./daemon-client-O6BYVRXV.js");
7420
- const result = await forwardExec(commandName, params, sessionName, extraOpts?.cdpEndpoint);
7893
+ const { forwardExec } = await import("./daemon-client-HEGAXWGK.js");
7894
+ const longRunning = /^(vision-task|crawl|scrape|record|replay)$/.test(commandName);
7895
+ const result = await forwardExec(
7896
+ commandName,
7897
+ params,
7898
+ sessionName,
7899
+ extraOpts?.cdpEndpoint,
7900
+ longRunning ? 20 * 6e4 : void 0
7901
+ );
7421
7902
  if (result) return result;
7422
7903
  }
7423
7904
  let session;
@@ -7585,7 +8066,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
7585
8066
  timestamp: start
7586
8067
  });
7587
8068
  if (isSuccess) {
7588
- return { ...ok25(raw.data, merged.length > 0 ? merged : raw.tips), duration, ...hookOutputs ? { hookOutputs } : {} };
8069
+ return { ...ok28(raw.data, merged.length > 0 ? merged : raw.tips), duration, ...hookOutputs ? { hookOutputs } : {} };
7589
8070
  }
7590
8071
  return { success: false, data: raw.data, message: raw.message, tips: mergedOrRaw, duration, ...hookOutputs ? { hookOutputs } : {} };
7591
8072
  }
@@ -7599,14 +8080,14 @@ async function executeCommand(commandName, params, sessionName = "default", extr
7599
8080
  duration,
7600
8081
  timestamp: start
7601
8082
  });
7602
- return { ...ok25(raw, smartTipNormalized), duration, ...hookOutputs ? { hookOutputs } : {} };
8083
+ return { ...ok28(raw, smartTipNormalized), duration, ...hookOutputs ? { hookOutputs } : {} };
7603
8084
  } catch (err) {
7604
8085
  const end = Date.now();
7605
8086
  const duration = end - start;
7606
8087
  const errorMessage = errMsg(err);
7607
8088
  if (session?.page && process.env.XBROWSER_RECOVERY && !extraOpts?._recoveryAttempted) {
7608
8089
  try {
7609
- const { attemptRecovery } = await import("./recovery-ZJVVHP7N.js");
8090
+ const { attemptRecovery } = await import("./recovery-L5GLDX4O.js");
7610
8091
  const recovery = await attemptRecovery(
7611
8092
  session.page,
7612
8093
  sessionName,
@@ -7644,7 +8125,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
7644
8125
  duration,
7645
8126
  timestamp: start
7646
8127
  });
7647
- return { ...fail11(errorMessage), duration };
8128
+ return { ...fail13(errorMessage), duration };
7648
8129
  } finally {
7649
8130
  }
7650
8131
  }
@@ -7685,7 +8166,7 @@ async function executeChain(input, options) {
7685
8166
  results.push({
7686
8167
  command: cmdName,
7687
8168
  raw: cmdStr,
7688
- ...fail11(`Plugin "${cmdName}" requires a sub-command`),
8169
+ ...fail13(`Plugin "${cmdName}" requires a sub-command`),
7689
8170
  duration: 0
7690
8171
  });
7691
8172
  if (type === "and") {
@@ -7704,7 +8185,7 @@ async function executeChain(input, options) {
7704
8185
  results.push({
7705
8186
  command: cmdName,
7706
8187
  raw: cmdStr,
7707
- ...fail11(`Unknown command "${subCommand}" for plugin "${cmdName}"`),
8188
+ ...fail13(`Unknown command "${subCommand}" for plugin "${cmdName}"`),
7708
8189
  duration: 0
7709
8190
  });
7710
8191
  if (type === "and") {
@@ -7808,7 +8289,7 @@ async function executeChain(input, options) {
7808
8289
  results.push({
7809
8290
  command: `${cmdName} ${subCommand}`,
7810
8291
  raw: cmdStr,
7811
- ...ok25(data),
8292
+ ...ok28(data),
7812
8293
  duration: duration2,
7813
8294
  ...hookOutputs ? { hookOutputs } : {}
7814
8295
  });
@@ -7836,7 +8317,7 @@ async function executeChain(input, options) {
7836
8317
  results.push({
7837
8318
  command: `${cmdName} ${subCommand}`,
7838
8319
  raw: cmdStr,
7839
- ...fail11(errorMessage),
8320
+ ...fail13(errorMessage),
7840
8321
  duration: duration2
7841
8322
  });
7842
8323
  if (type === "and") {
@@ -8150,25 +8631,25 @@ Run "xbrowser config list" to see current keys.`
8150
8631
 
8151
8632
  // src/plugin/installer.ts
8152
8633
  import {
8153
- existsSync as existsSync10,
8634
+ existsSync as existsSync12,
8154
8635
  readdirSync as readdirSync2,
8155
8636
  mkdirSync as mkdirSync8,
8156
8637
  rmSync as rmSync6,
8157
8638
  copyFileSync,
8158
8639
  cpSync as cpSync6,
8159
- readFileSync as readFileSync8,
8640
+ readFileSync as readFileSync10,
8160
8641
  writeFileSync as writeFileSync10
8161
8642
  } from "fs";
8162
8643
  import { resolve as resolve8, basename as basename2, dirname as dirname4 } from "path";
8163
8644
  import { homedir as homedir7 } from "os";
8164
8645
 
8165
8646
  // src/plugin/install-sources/local.ts
8166
- import { existsSync as existsSync5, cpSync, rmSync } from "fs";
8647
+ import { existsSync as existsSync7, cpSync, rmSync } from "fs";
8167
8648
  import { resolve as resolve3 } from "path";
8168
8649
  import { verifyPlugin, safeCleanup } from "@dyyz1993/xcli-core";
8169
8650
  async function installFromLocal(source, name, targetDir) {
8170
8651
  const srcPath = resolve3(source);
8171
- if (!existsSync5(srcPath)) {
8652
+ if (!existsSync7(srcPath)) {
8172
8653
  throw new Error(`Local path does not exist: ${srcPath}`);
8173
8654
  }
8174
8655
  const tmpTarget = `${targetDir}-tmp-${Date.now()}`;
@@ -8181,7 +8662,7 @@ async function installFromLocal(source, name, targetDir) {
8181
8662
  safeCleanup(tmpTarget);
8182
8663
  throw new Error(`Invalid plugin: ${verify.error}`);
8183
8664
  }
8184
- if (existsSync5(targetDir)) {
8665
+ if (existsSync7(targetDir)) {
8185
8666
  rmSync(targetDir, { recursive: true, force: true });
8186
8667
  }
8187
8668
  cpSync(tmpTarget, targetDir, { recursive: true, force: true });
@@ -8201,8 +8682,8 @@ async function installFromLocal(source, name, targetDir) {
8201
8682
  }
8202
8683
 
8203
8684
  // src/plugin/install-sources/npm.ts
8204
- import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync6, rmSync as rmSync2, cpSync as cpSync2 } from "fs";
8205
- import { resolve as resolve4, join as join7 } from "path";
8685
+ import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync6, rmSync as rmSync2, cpSync as cpSync2 } from "fs";
8686
+ import { resolve as resolve4, join as join9 } from "path";
8206
8687
  import { tmpdir } from "os";
8207
8688
  import {
8208
8689
  downloadToFile,
@@ -8275,13 +8756,13 @@ async function installFromNpm(packageName, name, targetDir) {
8275
8756
  throw new Error(`No tarball URL for ${packageName}@${latestVersion}`);
8276
8757
  }
8277
8758
  const tarballUrl = versionMeta.dist.tarball;
8278
- const tmpDir = join7(tmpdir(), `xbrowser-npm-${Date.now()}`);
8759
+ const tmpDir = join9(tmpdir(), `xbrowser-npm-${Date.now()}`);
8279
8760
  mkdirSync5(tmpDir, { recursive: true });
8280
8761
  let warnings = [];
8281
8762
  try {
8282
- const tarballPath = join7(tmpDir, `${name}.tgz`);
8763
+ const tarballPath = join9(tmpDir, `${name}.tgz`);
8283
8764
  await downloadToFile(tarballUrl, tarballPath);
8284
- const extractDir = join7(tmpDir, "extracted");
8765
+ const extractDir = join9(tmpDir, "extracted");
8285
8766
  extractTarGz(tarballPath, extractDir);
8286
8767
  flattenPackageRoot(extractDir);
8287
8768
  const verify = verifyPlugin2(extractDir, { metadataField: "xbrowser" });
@@ -8289,13 +8770,13 @@ async function installFromNpm(packageName, name, targetDir) {
8289
8770
  if (!verify.valid) {
8290
8771
  throw new Error(`Invalid npm plugin: ${verify.error}`);
8291
8772
  }
8292
- if (existsSync6(targetDir)) {
8773
+ if (existsSync8(targetDir)) {
8293
8774
  rmSync2(targetDir, { recursive: true, force: true });
8294
8775
  }
8295
8776
  cpSync2(extractDir, targetDir, { recursive: true, force: true });
8296
8777
  const pkgPath = resolve4(targetDir, "package.json");
8297
- if (existsSync6(pkgPath)) {
8298
- const pkg = JSON.parse(readFileSync4(pkgPath, "utf-8"));
8778
+ if (existsSync8(pkgPath)) {
8779
+ const pkg = JSON.parse(readFileSync6(pkgPath, "utf-8"));
8299
8780
  if (!pkg._npmSource) {
8300
8781
  pkg._npmSource = { name: packageName, version: latestVersion };
8301
8782
  writeFileSync6(pkgPath, JSON.stringify(pkg, null, 2));
@@ -8315,13 +8796,13 @@ async function installFromNpm(packageName, name, targetDir) {
8315
8796
  }
8316
8797
 
8317
8798
  // src/plugin/install-sources/git.ts
8318
- import { existsSync as existsSync7, readFileSync as readFileSync5, writeFileSync as writeFileSync7, rmSync as rmSync3, cpSync as cpSync3 } from "fs";
8319
- import { resolve as resolve5, join as join8 } from "path";
8799
+ import { existsSync as existsSync9, readFileSync as readFileSync7, writeFileSync as writeFileSync7, rmSync as rmSync3, cpSync as cpSync3 } from "fs";
8800
+ import { resolve as resolve5, join as join10 } from "path";
8320
8801
  import { tmpdir as tmpdir2 } from "os";
8321
8802
  import { execSync as execSync2 } from "child_process";
8322
8803
  import { verifyPlugin as verifyPlugin3, safeCleanup as safeCleanup3 } from "@dyyz1993/xcli-core";
8323
8804
  async function installFromGit(gitUrl, name, targetDir) {
8324
- const tmpDir = join8(tmpdir2(), `xbrowser-git-${Date.now()}`);
8805
+ const tmpDir = join10(tmpdir2(), `xbrowser-git-${Date.now()}`);
8325
8806
  let warnings = [];
8326
8807
  try {
8327
8808
  execSync2(`git clone --depth 1 "${gitUrl}" "${tmpDir}"`, { stdio: "pipe" });
@@ -8330,14 +8811,14 @@ async function installFromGit(gitUrl, name, targetDir) {
8330
8811
  if (!verify.valid) {
8331
8812
  throw new Error(`Invalid git plugin: ${verify.error}`);
8332
8813
  }
8333
- if (existsSync7(targetDir)) {
8814
+ if (existsSync9(targetDir)) {
8334
8815
  rmSync3(targetDir, { recursive: true, force: true });
8335
8816
  }
8336
8817
  cpSync3(tmpDir, targetDir, { recursive: true, force: true });
8337
8818
  rmSync3(resolve5(targetDir, ".git"), { recursive: true, force: true });
8338
8819
  const pkgPath = resolve5(targetDir, "package.json");
8339
- if (existsSync7(pkgPath)) {
8340
- const pkg = JSON.parse(readFileSync5(pkgPath, "utf-8"));
8820
+ if (existsSync9(pkgPath)) {
8821
+ const pkg = JSON.parse(readFileSync7(pkgPath, "utf-8"));
8341
8822
  if (!pkg._gitSource) {
8342
8823
  pkg._gitSource = { url: gitUrl };
8343
8824
  writeFileSync7(pkgPath, JSON.stringify(pkg, null, 2));
@@ -8357,8 +8838,8 @@ async function installFromGit(gitUrl, name, targetDir) {
8357
8838
  }
8358
8839
 
8359
8840
  // src/plugin/install-sources/url.ts
8360
- import { existsSync as existsSync8, mkdirSync as mkdirSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync8, rmSync as rmSync4, cpSync as cpSync4 } from "fs";
8361
- import { resolve as resolve6, join as join9, basename } from "path";
8841
+ import { existsSync as existsSync10, mkdirSync as mkdirSync6, readFileSync as readFileSync8, writeFileSync as writeFileSync8, rmSync as rmSync4, cpSync as cpSync4 } from "fs";
8842
+ import { resolve as resolve6, join as join11, basename } from "path";
8362
8843
  import { tmpdir as tmpdir3 } from "os";
8363
8844
  import {
8364
8845
  downloadToFile as downloadToFile2,
@@ -8368,14 +8849,14 @@ import {
8368
8849
  safeCleanup as safeCleanup4
8369
8850
  } from "@dyyz1993/xcli-core";
8370
8851
  async function installFromUrl(url, name, targetDir) {
8371
- const tmpDir = join9(tmpdir3(), `xbrowser-url-${Date.now()}`);
8852
+ const tmpDir = join11(tmpdir3(), `xbrowser-url-${Date.now()}`);
8372
8853
  mkdirSync6(tmpDir, { recursive: true });
8373
8854
  let warnings = [];
8374
8855
  try {
8375
8856
  const fileName = basename(new URL(url).pathname) || "plugin.tar.gz";
8376
- const tarballPath = join9(tmpDir, fileName);
8857
+ const tarballPath = join11(tmpDir, fileName);
8377
8858
  await downloadToFile2(url, tarballPath);
8378
- const extractDir = join9(tmpDir, "extracted");
8859
+ const extractDir = join11(tmpDir, "extracted");
8379
8860
  extractTarGz2(tarballPath, extractDir);
8380
8861
  flattenPackageRoot2(extractDir);
8381
8862
  const verify = verifyPlugin4(extractDir, { metadataField: "xbrowser" });
@@ -8383,13 +8864,13 @@ async function installFromUrl(url, name, targetDir) {
8383
8864
  if (!verify.valid) {
8384
8865
  throw new Error(`Invalid plugin from URL: ${verify.error}`);
8385
8866
  }
8386
- if (existsSync8(targetDir)) {
8867
+ if (existsSync10(targetDir)) {
8387
8868
  rmSync4(targetDir, { recursive: true, force: true });
8388
8869
  }
8389
8870
  cpSync4(extractDir, targetDir, { recursive: true, force: true });
8390
8871
  const pkgPath = resolve6(targetDir, "package.json");
8391
- if (existsSync8(pkgPath)) {
8392
- const pkg = JSON.parse(readFileSync6(pkgPath, "utf-8"));
8872
+ if (existsSync10(pkgPath)) {
8873
+ const pkg = JSON.parse(readFileSync8(pkgPath, "utf-8"));
8393
8874
  if (!pkg._urlSource) {
8394
8875
  pkg._urlSource = { url };
8395
8876
  writeFileSync8(pkgPath, JSON.stringify(pkg, null, 2));
@@ -8410,14 +8891,14 @@ async function installFromUrl(url, name, targetDir) {
8410
8891
 
8411
8892
  // src/plugin/install-sources/marketplace.ts
8412
8893
  import {
8413
- existsSync as existsSync9,
8894
+ existsSync as existsSync11,
8414
8895
  mkdirSync as mkdirSync7,
8415
8896
  writeFileSync as writeFileSync9,
8416
- readFileSync as readFileSync7,
8897
+ readFileSync as readFileSync9,
8417
8898
  rmSync as rmSync5,
8418
8899
  cpSync as cpSync5
8419
8900
  } from "fs";
8420
- import { resolve as resolve7, join as join10, dirname as dirname3 } from "path";
8901
+ import { resolve as resolve7, join as join12, dirname as dirname3 } from "path";
8421
8902
  import { tmpdir as tmpdir4 } from "os";
8422
8903
  import { gunzipSync } from "zlib";
8423
8904
  import {
@@ -8442,11 +8923,11 @@ async function installFromMarketplace(pluginsDir, slug, options) {
8442
8923
  const plugin = detailData.data;
8443
8924
  const name = options?.name || String(plugin.slug || slug);
8444
8925
  const targetDir = resolve7(pluginsDir, name);
8445
- if (existsSync9(targetDir) && !options?.force) {
8926
+ if (existsSync11(targetDir) && !options?.force) {
8446
8927
  throw new Error(`Plugin "${name}" already exists. Use --force to overwrite.`);
8447
8928
  }
8448
8929
  mkdirSync7(targetDir, { recursive: true });
8449
- const tmpDir = join10(tmpdir4(), `xbrowser-marketplace-${Date.now()}`);
8930
+ const tmpDir = join12(tmpdir4(), `xbrowser-marketplace-${Date.now()}`);
8450
8931
  mkdirSync7(tmpDir, { recursive: true });
8451
8932
  const realSlug = String(plugin.slug || slug);
8452
8933
  try {
@@ -8477,7 +8958,7 @@ function isManifestArray(data) {
8477
8958
  return Array.isArray(data) && data.length > 0 && typeof data[0].path === "string" && typeof data[0].content === "string";
8478
8959
  }
8479
8960
  function extractManifestToDir(manifest, targetDir) {
8480
- if (existsSync9(targetDir)) {
8961
+ if (existsSync11(targetDir)) {
8481
8962
  rmSync5(targetDir, { recursive: true, force: true });
8482
8963
  }
8483
8964
  mkdirSync7(targetDir, { recursive: true });
@@ -8509,18 +8990,18 @@ async function downloadAndExtractMarketplaceTarball(baseUrl, slug, tmpDir, targe
8509
8990
  }
8510
8991
  if (tarballRes.status === 302 || tarballRes.headers.get("location")) {
8511
8992
  const redirectUrl = tarballRes.headers.get("location");
8512
- const tarballPath = join10(tmpDir, `${slug}.tar.gz`);
8993
+ const tarballPath = join12(tmpDir, `${slug}.tar.gz`);
8513
8994
  await downloadToFile3(redirectUrl, tarballPath);
8514
- const buffer = readFileSync7(tarballPath);
8995
+ const buffer = readFileSync9(tarballPath);
8515
8996
  const manifest = tryParseAsGzippedManifest(buffer);
8516
8997
  if (manifest) {
8517
8998
  extractManifestToDir(manifest, targetDir);
8518
8999
  return;
8519
9000
  }
8520
- const extractDir = join10(tmpDir, "extracted");
9001
+ const extractDir = join12(tmpDir, "extracted");
8521
9002
  extractTarGz3(tarballPath, extractDir);
8522
9003
  flattenPackageRoot3(extractDir);
8523
- if (existsSync9(targetDir)) {
9004
+ if (existsSync11(targetDir)) {
8524
9005
  rmSync5(targetDir, { recursive: true, force: true });
8525
9006
  }
8526
9007
  cpSync5(extractDir, targetDir, { recursive: true, force: true });
@@ -8531,13 +9012,13 @@ async function downloadAndExtractMarketplaceTarball(baseUrl, slug, tmpDir, targe
8531
9012
  extractManifestToDir(manifest, targetDir);
8532
9013
  return;
8533
9014
  }
8534
- const tarballPath = join10(tmpDir, `${slug}.tar.gz`);
9015
+ const tarballPath = join12(tmpDir, `${slug}.tar.gz`);
8535
9016
  writeFileSync9(tarballPath, buffer);
8536
9017
  try {
8537
- const extractDir = join10(tmpDir, "extracted");
9018
+ const extractDir = join12(tmpDir, "extracted");
8538
9019
  extractTarGz3(tarballPath, extractDir);
8539
9020
  flattenPackageRoot3(extractDir);
8540
- if (existsSync9(targetDir)) {
9021
+ if (existsSync11(targetDir)) {
8541
9022
  rmSync5(targetDir, { recursive: true, force: true });
8542
9023
  }
8543
9024
  cpSync5(extractDir, targetDir, { recursive: true, force: true });
@@ -8573,11 +9054,11 @@ function writeMarketplacePackageJson(plugin, slug, name, baseUrl, targetDir) {
8573
9054
  }
8574
9055
  };
8575
9056
  const pkgPath = resolve7(targetDir, "package.json");
8576
- if (!existsSync9(pkgPath)) {
9057
+ if (!existsSync11(pkgPath)) {
8577
9058
  writeFileSync9(pkgPath, JSON.stringify(packageJson, null, 2));
8578
9059
  } else {
8579
9060
  try {
8580
- const existing = JSON.parse(readFileSync7(pkgPath, "utf-8"));
9061
+ const existing = JSON.parse(readFileSync9(pkgPath, "utf-8"));
8581
9062
  const merged = {
8582
9063
  ...existing,
8583
9064
  xbrowser: { ...existing.xbrowser, ...packageJson.xbrowser },
@@ -8590,7 +9071,7 @@ function writeMarketplacePackageJson(plugin, slug, name, baseUrl, targetDir) {
8590
9071
  }
8591
9072
  }
8592
9073
  function ensureIndexFile(plugin, name, targetDir) {
8593
- if (!existsSync9(resolve7(targetDir, "index.ts")) && !existsSync9(resolve7(targetDir, "index.js"))) {
9074
+ if (!existsSync11(resolve7(targetDir, "index.ts")) && !existsSync11(resolve7(targetDir, "index.js"))) {
8594
9075
  const commands = plugin.commands || [];
8595
9076
  const commandHandlers = commands.length > 0 ? commands.map((cmd) => {
8596
9077
  return [
@@ -8644,7 +9125,7 @@ var PluginInstaller = class {
8644
9125
  const type = this.detectSourceType(source);
8645
9126
  const name = options?.name || this.deriveName(source, type);
8646
9127
  const targetDir = resolve8(this.pluginsDir, name);
8647
- if (existsSync10(targetDir) && !options?.force) {
9128
+ if (existsSync12(targetDir) && !options?.force) {
8648
9129
  throw new Error(`Plugin "${name}" already exists. Use --force to overwrite.`);
8649
9130
  }
8650
9131
  mkdirSync8(targetDir, { recursive: true });
@@ -8687,10 +9168,10 @@ var PluginInstaller = class {
8687
9168
  */
8688
9169
  async fixSharedDeps(pluginDir) {
8689
9170
  const indexPath = resolve8(pluginDir, "index.ts");
8690
- if (!existsSync10(indexPath)) return;
9171
+ if (!existsSync12(indexPath)) return;
8691
9172
  let content;
8692
9173
  try {
8693
- content = readFileSync8(indexPath, "utf8");
9174
+ content = readFileSync10(indexPath, "utf8");
8694
9175
  } catch {
8695
9176
  return;
8696
9177
  }
@@ -8705,7 +9186,7 @@ var PluginInstaller = class {
8705
9186
  const toCopy = [];
8706
9187
  for (const file of missingFiles) {
8707
9188
  const targetPath = resolve8(sharedDir, file);
8708
- if (!existsSync10(targetPath)) {
9189
+ if (!existsSync12(targetPath)) {
8709
9190
  toCopy.push(file);
8710
9191
  }
8711
9192
  }
@@ -8716,7 +9197,7 @@ var PluginInstaller = class {
8716
9197
  ];
8717
9198
  let sourceSharedDir = null;
8718
9199
  for (const dir of repoSharedDirs) {
8719
- if (existsSync10(dir)) {
9200
+ if (existsSync12(dir)) {
8720
9201
  sourceSharedDir = dir;
8721
9202
  break;
8722
9203
  }
@@ -8733,7 +9214,7 @@ var PluginInstaller = class {
8733
9214
  const content2 = await resp.text();
8734
9215
  const dst = resolve8(sharedDir, file);
8735
9216
  const dstDir = dirname4(dst);
8736
- if (!existsSync10(dstDir)) mkdirSync8(dstDir, { recursive: true });
9217
+ if (!existsSync12(dstDir)) mkdirSync8(dstDir, { recursive: true });
8737
9218
  writeFileSync10(dst, content2, "utf-8");
8738
9219
  console.log(`\u2705 Downloaded shared/${file} from GitHub`);
8739
9220
  } else {
@@ -8755,7 +9236,7 @@ var PluginInstaller = class {
8755
9236
  for (const file of toCopy) {
8756
9237
  const src = resolve8(sourceSharedDir, file);
8757
9238
  const dst = resolve8(sharedDir, file);
8758
- if (existsSync10(src)) {
9239
+ if (existsSync12(src)) {
8759
9240
  try {
8760
9241
  cpSync6(dirname4(src), dirname4(dst), { recursive: true });
8761
9242
  console.log(`\u2705 Copied shared/${file} for plugin "${basename2(pluginDir)}"`);
@@ -8805,7 +9286,7 @@ var PluginInstaller = class {
8805
9286
  */
8806
9287
  async uninstall(name) {
8807
9288
  const targetDir = resolve8(this.pluginsDir, name);
8808
- if (!existsSync10(targetDir)) {
9289
+ if (!existsSync12(targetDir)) {
8809
9290
  throw new Error(`Plugin "${name}" not found`);
8810
9291
  }
8811
9292
  rmSync6(targetDir, { recursive: true, force: true });
@@ -8816,7 +9297,7 @@ var PluginInstaller = class {
8816
9297
  * @returns Array of installed plugin information.
8817
9298
  */
8818
9299
  async list(_options) {
8819
- if (!existsSync10(this.pluginsDir)) return [];
9300
+ if (!existsSync12(this.pluginsDir)) return [];
8820
9301
  const entries = readdirSync2(this.pluginsDir, { withFileTypes: true });
8821
9302
  const plugins = [];
8822
9303
  for (const entry of entries) {
@@ -8824,7 +9305,7 @@ var PluginInstaller = class {
8824
9305
  const pluginPath = resolve8(this.pluginsDir, entry.name);
8825
9306
  const indexPath = resolve8(pluginPath, "index.ts");
8826
9307
  const indexJsPath = resolve8(pluginPath, "index.js");
8827
- if (!existsSync10(indexPath) && !existsSync10(indexJsPath)) continue;
9308
+ if (!existsSync12(indexPath) && !existsSync12(indexJsPath)) continue;
8828
9309
  const metadata = PluginMetadataParser.parseFromPackageJson(pluginPath);
8829
9310
  let source = "local";
8830
9311
  const pkg = readJsonFile(resolve8(pluginPath, "package.json"), {});
@@ -8851,10 +9332,10 @@ var PluginInstaller = class {
8851
9332
  }
8852
9333
  if (source.startsWith("file://")) {
8853
9334
  const filePath = decodeURIComponent(new URL(source).pathname);
8854
- if (existsSync10(filePath)) return "url";
9335
+ if (existsSync12(filePath)) return "url";
8855
9336
  }
8856
9337
  if (source.endsWith(".git") || source.includes("github.com/")) return "git";
8857
- if (existsSync10(resolve8(source))) return "local";
9338
+ if (existsSync12(resolve8(source))) return "local";
8858
9339
  return "npm";
8859
9340
  }
8860
9341
  deriveName(source, type) {
@@ -9783,7 +10264,7 @@ var previewBuiltin = {
9783
10264
 
9784
10265
  // src/builtins/knowledge.ts
9785
10266
  init_site_knowledge();
9786
- import { existsSync as existsSync12 } from "fs";
10267
+ import { existsSync as existsSync14 } from "fs";
9787
10268
  var knowledgeBuiltin = {
9788
10269
  name: "knowledge",
9789
10270
  description: "View LLM-readable site knowledge base (selectors, forms, APIs)",
@@ -9962,8 +10443,8 @@ var knowledgeBuiltin = {
9962
10443
  }
9963
10444
  const mdPath = getKnowledgePath(domain, "md");
9964
10445
  const jsonPath = getKnowledgePath(domain, "json");
9965
- console.log(`Markdown: ${mdPath} (${existsSync12(mdPath) ? "exists" : "not found"})`);
9966
- console.log(`JSON: ${jsonPath} (${existsSync12(jsonPath) ? "exists" : "not found"})`);
10446
+ console.log(`Markdown: ${mdPath} (${existsSync14(mdPath) ? "exists" : "not found"})`);
10447
+ console.log(`JSON: ${jsonPath} (${existsSync14(jsonPath) ? "exists" : "not found"})`);
9967
10448
  return;
9968
10449
  }
9969
10450
  console.error(`Unknown subcommand: ${subcommand}`);
@@ -10065,10 +10546,12 @@ var HTML_TAGS = /* @__PURE__ */ new Set([
10065
10546
  "map",
10066
10547
  "area"
10067
10548
  ]);
10549
+ var SELECTOR_DSL_PREFIXES = /^(text=|popup-text=|xpath=|role=|css=|chain=)/;
10068
10550
  function normalizeSelector(input) {
10069
10551
  if (!input) return input;
10070
10552
  if (/^[#\.\[\:\/]/.test(input)) return input;
10071
10553
  if (input === "*") return input;
10554
+ if (SELECTOR_DSL_PREFIXES.test(input)) return input;
10072
10555
  if (/[>\s+,~]/.test(input)) return input;
10073
10556
  const tagName = input.match(/^[a-zA-Z][a-zA-Z0-9]*/)?.[0];
10074
10557
  if (tagName && HTML_TAGS.has(tagName.toLowerCase())) return input;
@@ -10243,11 +10726,29 @@ async function handleBrowserCommand(command, args, options, sessionName, mode, c
10243
10726
  output: options.output || options.o || args[0]
10244
10727
  };
10245
10728
  break;
10246
- case "eval":
10729
+ case "ua": {
10730
+ cmdName = "ua";
10731
+ const val = args.join(" ").trim() || options.value || "";
10732
+ params = { value: val };
10733
+ break;
10734
+ }
10735
+ case "eval": {
10247
10736
  cmdName = "eval";
10248
- params = { expression: args.join(" ") || options.expression || "" };
10249
- if (!params.expression) outputError("Usage: xbrowser eval <expression>");
10737
+ const frameVal = options.frame || void 0;
10738
+ const exprParts = [];
10739
+ for (let i = 0; i < args.length; i++) {
10740
+ if (args[i] === "--frame") {
10741
+ i++;
10742
+ continue;
10743
+ }
10744
+ if (args[i].startsWith("--frame=")) continue;
10745
+ exprParts.push(args[i]);
10746
+ }
10747
+ params = { expression: exprParts.join(" ") || options.expression || "" };
10748
+ if (frameVal) params.frame = frameVal;
10749
+ if (!params.expression) outputError("Usage: xbrowser eval <expression> [--frame <url-substring>]");
10250
10750
  break;
10751
+ }
10251
10752
  case "scroll": {
10252
10753
  let direction = "down";
10253
10754
  let distance;
@@ -10539,14 +11040,14 @@ async function handleBrowserCommand(command, args, options, sessionName, mode, c
10539
11040
 
10540
11041
  // src/cli/session-routes.ts
10541
11042
  import { homedir as homedir8 } from "os";
10542
- import { join as join12 } from "path";
11043
+ import { join as join14 } from "path";
10543
11044
  import { readdirSync as readdirSync3, rmSync as rmSync7 } from "fs";
10544
11045
  function cleanSessionFiles() {
10545
- const dir = join12(homedir8(), ".xbrowser", "sessions");
11046
+ const dir = join14(homedir8(), ".xbrowser", "sessions");
10546
11047
  let count = 0;
10547
11048
  try {
10548
11049
  for (const entry of readdirSync3(dir, { withFileTypes: true })) {
10549
- const p = join12(dir, entry.name);
11050
+ const p = join14(dir, entry.name);
10550
11051
  rmSync7(p, { recursive: true, force: true });
10551
11052
  count++;
10552
11053
  }
@@ -10950,7 +11451,7 @@ async function handlePlugin(args, options, mode) {
10950
11451
  } catch {
10951
11452
  }
10952
11453
  try {
10953
- const { daemonPing } = await import("./daemon-client-O6BYVRXV.js");
11454
+ const { daemonPing } = await import("./daemon-client-HEGAXWGK.js");
10954
11455
  if (await daemonPing()) {
10955
11456
  await fetch("http://localhost:9224/rpc", {
10956
11457
  method: "POST",
@@ -11100,7 +11601,7 @@ function handleDaemon(args, options, mode) {
11100
11601
  }
11101
11602
 
11102
11603
  // src/cli/record-routes.ts
11103
- import { existsSync as existsSync13 } from "fs";
11604
+ import { existsSync as existsSync15 } from "fs";
11104
11605
  async function handleRecord(args, options, mode) {
11105
11606
  const sub = args[0];
11106
11607
  switch (sub) {
@@ -11131,9 +11632,9 @@ async function handleRecord(args, options, mode) {
11131
11632
  outputError(String(result.error || "Failed to stop recording"));
11132
11633
  return;
11133
11634
  }
11134
- if (output && !existsSync13(output)) {
11635
+ if (output && !existsSync15(output)) {
11135
11636
  const defaultRecording = SessionRecorder.getRecordingsDir(sessionName) + "/recording.json";
11136
- if (existsSync13(defaultRecording)) {
11637
+ if (existsSync15(defaultRecording)) {
11137
11638
  try {
11138
11639
  const { mkdirSync: mkdirSync11, copyFileSync: copyFileSync2 } = await import("fs");
11139
11640
  const { dirname: pathDirname } = await import("path");
@@ -11516,10 +12017,10 @@ async function handleFilter(args, _mode, options) {
11516
12017
  }
11517
12018
  }
11518
12019
  async function handleGeneratePlugin(sessionName, pluginName, outputDir) {
11519
- const { SessionRecorder: SessionRecorder2 } = await import("./session-recorder-QRZMKFVL.js");
12020
+ const { SessionRecorder: SessionRecorder2 } = await import("./session-recorder-SLDBENVF.js");
11520
12021
  const { readSiteKnowledge: readSiteKnowledge2, toMarkdown } = await import("./site-knowledge-SYC6VCDB.js");
11521
12022
  const { mkdirSync: mkdirSync11, writeFileSync: writeFileSync13 } = await import("fs");
11522
- const { join: join14 } = await import("path");
12023
+ const { join: join16 } = await import("path");
11523
12024
  const data = SessionRecorder2.readData(sessionName);
11524
12025
  if (!data) {
11525
12026
  outputError(`No recording found for session "${sessionName}". Run \`xbrowser record stop --session ${sessionName}\` first.`);
@@ -11531,14 +12032,14 @@ async function handleGeneratePlugin(sessionName, pluginName, outputDir) {
11531
12032
  } catch {
11532
12033
  }
11533
12034
  const finalPluginName = pluginName || domain.split(".")[0] || "my-site";
11534
- const finalOutputDir = outputDir || join14(process.cwd(), ".xcli", "plugins", finalPluginName);
12035
+ const finalOutputDir = outputDir || join16(process.cwd(), ".xcli", "plugins", finalPluginName);
11535
12036
  const knowledge = readSiteKnowledge2(domain);
11536
12037
  const knowledgeMd = knowledge ? toMarkdown(knowledge) : "";
11537
12038
  const pluginCode = generatePluginCode(finalPluginName, domain, data, knowledgeMd);
11538
- mkdirSync11(join14(finalOutputDir), { recursive: true });
11539
- writeFileSync13(join14(finalOutputDir, "index.ts"), pluginCode, "utf-8");
12039
+ mkdirSync11(join16(finalOutputDir), { recursive: true });
12040
+ writeFileSync13(join16(finalOutputDir, "index.ts"), pluginCode, "utf-8");
11540
12041
  if (knowledgeMd) {
11541
- writeFileSync13(join14(finalOutputDir, "SITE_KNOWLEDGE.md"), knowledgeMd, "utf-8");
12042
+ writeFileSync13(join16(finalOutputDir, "SITE_KNOWLEDGE.md"), knowledgeMd, "utf-8");
11542
12043
  }
11543
12044
  console.log("");
11544
12045
  console.log("=== Plugin Generated ===");
@@ -12081,7 +12582,7 @@ async function handleNetCommand(args, options, mode, sessionName) {
12081
12582
 
12082
12583
  // src/cli/test-routes.ts
12083
12584
  import { execSync as execSync3 } from "child_process";
12084
- import { readFileSync as readFileSync9 } from "fs";
12585
+ import { readFileSync as readFileSync11 } from "fs";
12085
12586
  import { resolve as resolve9 } from "path";
12086
12587
  function findPluginPath(plugin) {
12087
12588
  const candidates = [
@@ -12090,7 +12591,7 @@ function findPluginPath(plugin) {
12090
12591
  ];
12091
12592
  for (const p of candidates) {
12092
12593
  try {
12093
- readFileSync9(p, "utf-8");
12594
+ readFileSync11(p, "utf-8");
12094
12595
  return p;
12095
12596
  } catch {
12096
12597
  }
@@ -12101,7 +12602,7 @@ function extractSchema(plugin, command) {
12101
12602
  const pluginPath = findPluginPath(plugin);
12102
12603
  let src;
12103
12604
  try {
12104
- src = readFileSync9(pluginPath, "utf-8");
12605
+ src = readFileSync11(pluginPath, "utf-8");
12105
12606
  } catch {
12106
12607
  return null;
12107
12608
  }
@@ -12822,6 +13323,9 @@ var HTTPServer = class {
12822
13323
  this.server = null;
12823
13324
  reject(err);
12824
13325
  });
13326
+ server.headersTimeout = 20 * 6e4;
13327
+ server.requestTimeout = 20 * 6e4;
13328
+ server.keepAliveTimeout = 20 * 6e4;
12825
13329
  server.listen(this.port, this.host, () => {
12826
13330
  const addr = server.address();
12827
13331
  if (addr && typeof addr === "object") {
@@ -13021,7 +13525,7 @@ function extractSessionNameFromArgv(argv) {
13021
13525
  async function handleEvalMode(argv) {
13022
13526
  const evalCommands = parseEvalFlags(argv);
13023
13527
  if (evalCommands.length === 0) return;
13024
- const chain = evalCommands.join(" ; ");
13528
+ const chain = evalCommands.map((script) => `eval --script-b64 ${Buffer.from(script, "utf8").toString("base64url")}`).join(" ; ");
13025
13529
  const cdpEndpoint = extractCdpFromArgv(argv);
13026
13530
  const sessionArgIdx = argv.indexOf("--session");
13027
13531
  const sessionName = sessionArgIdx >= 0 && argv[sessionArgIdx + 1] ? argv[sessionArgIdx + 1] : process.env.XBROWSER_SESSION || "default";
@@ -13029,6 +13533,51 @@ async function handleEvalMode(argv) {
13029
13533
  printChainResult(chainResult);
13030
13534
  if (!chainResult.success) throw new Error("Command failed");
13031
13535
  }
13536
+ function findEvalStart(argv) {
13537
+ const flagsWithValue = /* @__PURE__ */ new Set(["--session", "--cdp"]);
13538
+ for (let i = 0; i < argv.length; i++) {
13539
+ const a = argv[i];
13540
+ if (a === "-e" || a === "--eval") return -1;
13541
+ if (a.startsWith("--")) {
13542
+ if (flagsWithValue.has(a)) {
13543
+ i++;
13544
+ continue;
13545
+ }
13546
+ continue;
13547
+ }
13548
+ return a === "eval" ? i : -1;
13549
+ }
13550
+ return -1;
13551
+ }
13552
+ async function handleEvalScript(parts, argv) {
13553
+ const cdpEndpoint = extractCdpFromArgv(argv);
13554
+ const sessionName = extractSessionNameFromArgv(argv);
13555
+ let frame;
13556
+ const scriptLines = [];
13557
+ for (let i = 0; i < parts.length; i++) {
13558
+ const part = parts[i];
13559
+ if (part === "--frame" && parts[i + 1]) {
13560
+ frame = parts[i + 1];
13561
+ i++;
13562
+ continue;
13563
+ }
13564
+ if (part === "--session" || part === "--cdp") {
13565
+ i++;
13566
+ continue;
13567
+ }
13568
+ scriptLines.push(part);
13569
+ }
13570
+ const script = scriptLines.join("\n");
13571
+ if (!script.trim()) {
13572
+ console.error("Error: eval requires a script (usage: xbrowser eval <js...>)");
13573
+ throw new Error("Command failed");
13574
+ }
13575
+ const b64 = Buffer.from(script, "utf8").toString("base64url");
13576
+ const chain = frame ? `eval --frame ${frame} --script-b64 ${b64}` : `eval --script-b64 ${b64}`;
13577
+ const chainResult = await executeChain(chain, { cdpEndpoint, sessionName });
13578
+ printChainResult(chainResult);
13579
+ if (!chainResult.success) throw new Error("Command failed");
13580
+ }
13032
13581
  async function handleChainInput(input, argv) {
13033
13582
  const cdpEndpoint = argv ? extractCdpFromArgv(argv) : void 0;
13034
13583
  const hasJson = argv ? argv.some((a) => a === "--json" || a.startsWith("--json=") || a.includes(" --json") || a.startsWith("--json")) || argv.includes("-j") : false;
@@ -13066,6 +13615,11 @@ async function routeCommand(argvIn, stdinCommands) {
13066
13615
  await handleStdinMode(stdinCommands, argv);
13067
13616
  return;
13068
13617
  }
13618
+ const evalStart = findEvalStart(argv);
13619
+ if (evalStart >= 0 && argv.length > evalStart + 1) {
13620
+ await handleEvalScript(argv.slice(evalStart + 1), argv);
13621
+ return;
13622
+ }
13069
13623
  if (parseEvalFlags(argv).length > 0) {
13070
13624
  await handleEvalMode(argv);
13071
13625
  return;
@@ -13457,7 +14011,7 @@ Run "xbrowser ${command} ${subCommand} --help" to see available parameters.`
13457
14011
  }
13458
14012
  const needsBrowser = cmdEntry.scope === "page" || cmdEntry.scope === "browser";
13459
14013
  if (needsBrowser && !process.env.XBROWSER_DAEMON_WORKER) {
13460
- const { forwardPluginExec } = await import("./daemon-client-O6BYVRXV.js");
14014
+ const { forwardPluginExec } = await import("./daemon-client-HEGAXWGK.js");
13461
14015
  const userTimeout = typeof params.timeout === "number" && params.timeout > 0 ? params.timeout * 1e3 + 3e4 : void 0;
13462
14016
  const result = await forwardPluginExec(`${command}.${subCommand}`, params, sessionName, cdpEndpoint, userTimeout);
13463
14017
  const resultData = result && typeof result === "object" && "data" in result ? result.data : void 0;
@@ -13497,7 +14051,7 @@ Run "xbrowser ${command} ${subCommand} --help" to see available parameters.`
13497
14051
  const targetPage = pages[cmdTabIndex];
13498
14052
  await targetPage.bringToFront().catch(() => {
13499
14053
  });
13500
- const { setActivePage: setActivePage2 } = await import("./browser-HBL72GPZ.js");
14054
+ const { setActivePage: setActivePage2 } = await import("./browser-3HQKDZQ6.js");
13501
14055
  setActivePage2(session, targetPage);
13502
14056
  }
13503
14057
  }
@@ -15070,12 +15624,12 @@ var FileListHandler = class {
15070
15624
  const msg = ctx.message;
15071
15625
  try {
15072
15626
  const { readdirSync: readdirSync4, statSync } = await import("fs");
15073
- const { join: join14, resolve: resolve10 } = await import("path");
15627
+ const { join: join16, resolve: resolve10 } = await import("path");
15074
15628
  const targetPath = resolve10(msg.path);
15075
15629
  const entries = readdirSync4(targetPath);
15076
15630
  const files = entries.map((name) => {
15077
15631
  try {
15078
- const stat = statSync(join14(targetPath, name));
15632
+ const stat = statSync(join16(targetPath, name));
15079
15633
  return { name, isDir: stat.isDirectory(), size: stat.size, modified: stat.mtime.toISOString() };
15080
15634
  } catch {
15081
15635
  return { name, isDir: false, size: 0, modified: "" };
@@ -15092,10 +15646,10 @@ var FileDownloadHandler = class {
15092
15646
  async handle(ctx) {
15093
15647
  const msg = ctx.message;
15094
15648
  try {
15095
- const { readFileSync: readFileSync11 } = await import("fs");
15649
+ const { readFileSync: readFileSync13 } = await import("fs");
15096
15650
  const { resolve: resolve10, basename: basename3 } = await import("path");
15097
15651
  const targetPath = resolve10(msg.path);
15098
- const data = readFileSync11(targetPath);
15652
+ const data = readFileSync13(targetPath);
15099
15653
  const base64 = data.toString("base64");
15100
15654
  const ext = targetPath.split(".").pop()?.toLowerCase() || "";
15101
15655
  const mimeMap = {
@@ -16670,7 +17224,7 @@ var DataCollector = class {
16670
17224
  return results;
16671
17225
  }
16672
17226
  async createBrowserContext() {
16673
- const { launch } = await import("./cdp-driver-J3YQ4LJV.js");
17227
+ const { launch } = await import("./cdp-driver-YIKR4BUX.js");
16674
17228
  const { browser } = await launch({
16675
17229
  headless: true,
16676
17230
  args: ["--no-sandbox", "--disable-setuid-sandbox"]
@@ -16710,7 +17264,7 @@ var DataCollector = class {
16710
17264
 
16711
17265
  // src/cdp-interceptor/index.ts
16712
17266
  async function createCDPInterceptor(config) {
16713
- const { CDPInterceptorProxy: CDPInterceptorProxy2 } = await import("./proxy-C6CK3UH5.js");
17267
+ const { CDPInterceptorProxy: CDPInterceptorProxy2 } = await import("./proxy-LUR4U5YF.js");
16714
17268
  const proxy = new CDPInterceptorProxy2(config);
16715
17269
  await proxy.start();
16716
17270
  return proxy;