@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
@@ -15,12 +15,11 @@ import {
15
15
  } from "./chunk-VEDJ5XSQ.js";
16
16
  import {
17
17
  SessionRecorder
18
- } from "./chunk-5UGR6MUK.js";
18
+ } from "./chunk-HBHOKZPN.js";
19
19
  import {
20
20
  closeEphemeralContext,
21
21
  closeSessionByName,
22
22
  createEphemeralContext,
23
- createRuleEngine,
24
23
  createSession,
25
24
  findOrRestoreSession,
26
25
  findSession,
@@ -31,8 +30,13 @@ import {
31
30
  resolveLaunchOpts,
32
31
  saveSessionDiskMeta,
33
32
  setActivePage
34
- } from "./chunk-BAHSRZIX.js";
35
- import "./chunk-RRBXV7KE.js";
33
+ } from "./chunk-KJTABK3Z.js";
34
+ import {
35
+ createRuleEngine
36
+ } from "./chunk-Z6CUR3VG.js";
37
+ import {
38
+ queryJS
39
+ } from "./chunk-3FWLW7FS.js";
36
40
  import "./chunk-TNEN6VQ2.js";
37
41
  import {
38
42
  errMsg
@@ -40,20 +44,21 @@ import {
40
44
  import {
41
45
  detectAntiBot,
42
46
  formatDetectionMessage
43
- } from "./chunk-INTQPBYF.js";
47
+ } from "./chunk-JKVUFP3G.js";
44
48
  import {
45
- ScreencastCapturer
46
- } from "./chunk-BNO7OKO4.js";
49
+ ScreencastCapturer,
50
+ resolveScreenshotsDir
51
+ } from "./chunk-HBMEFSTB.js";
47
52
  import "./chunk-KFQGP6VL.js";
48
53
 
49
54
  // src/daemon/daemon-main.ts
50
55
  import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, appendFileSync, unlinkSync } from "fs";
51
- import { join as join7 } from "path";
56
+ import { join as join9 } from "path";
52
57
  import { homedir as homedir7 } from "os";
53
58
  import { startHttpServer } from "@dyyz1993/xcli-core";
54
59
 
55
60
  // src/daemon/rpc-handlers.ts
56
- import { readFileSync as readFileSync5 } from "fs";
61
+ import { readFileSync as readFileSync7 } from "fs";
57
62
  import {
58
63
  createSessionMeta,
59
64
  removeSession
@@ -61,8 +66,8 @@ import {
61
66
 
62
67
  // src/executor.ts
63
68
  import {
64
- ok as ok25,
65
- fail as fail11,
69
+ ok as ok28,
70
+ fail as fail13,
66
71
  isCommandResult,
67
72
  CompositeStorage as CompositeStorage2,
68
73
  TipCollector as TipCollector2,
@@ -416,14 +421,23 @@ var CAPTCHA_SELECTORS = {
416
421
  generic: '#captcha, .captcha-container, [class*="captcha"]'
417
422
  };
418
423
  async function detectCaptcha(page) {
419
- for (const [type, selector] of Object.entries(CAPTCHA_SELECTORS)) {
420
- const el = await page.$(selector);
421
- if (el) {
422
- const iframeUrl = await el.getAttribute("src").catch(() => void 0);
423
- return { type, selector, iframeUrl: iframeUrl || void 0 };
424
- }
425
- }
426
- return null;
424
+ const hit = await page.evaluate(`
425
+ (function() {
426
+ var S = ${JSON.stringify(CAPTCHA_SELECTORS)};
427
+ for (var type in S) {
428
+ var sel = S[type];
429
+ var el;
430
+ try { el = document.querySelector(sel); } catch (e) { continue; }
431
+ if (el) {
432
+ var url = el.getAttribute && el.getAttribute('src');
433
+ return { type: type, selector: sel, iframeUrl: url || undefined };
434
+ }
435
+ }
436
+ return null;
437
+ })()
438
+ `).catch(() => null);
439
+ if (!hit) return null;
440
+ return { type: hit.type, selector: hit.selector, iframeUrl: hit.iframeUrl || void 0 };
427
441
  }
428
442
  async function waitForCaptchaSolved(page, captchaInfo, timeoutMs = 18e4) {
429
443
  const startTime = Date.now();
@@ -474,12 +488,24 @@ var clickCommand = registerCommand({
474
488
  let detectedNewPage;
475
489
  let cleanup;
476
490
  if (ctx.browserContext?.on) {
491
+ const targetIdOf = (p2) => p2._targetId;
492
+ let targetIdsBefore;
493
+ try {
494
+ targetIdsBefore = new Set(ctx.browserContext.pages().map(targetIdOf));
495
+ } catch {
496
+ targetIdsBefore = /* @__PURE__ */ new Set();
497
+ }
477
498
  const pagePromise = new Promise((resolve) => {
478
499
  const timer = setTimeout(() => {
479
500
  ctx.browserContext.off("page", handler);
480
501
  resolve(void 0);
481
502
  }, 3e3);
482
503
  const handler = (page2) => {
504
+ if (targetIdsBefore.has(targetIdOf(page2))) return;
505
+ try {
506
+ if ((page2.url?.() ?? "") === "about:blank") return;
507
+ } catch {
508
+ }
483
509
  clearTimeout(timer);
484
510
  ctx.browserContext.off("page", handler);
485
511
  resolve(page2);
@@ -880,9 +906,11 @@ var mouseCommand = registerCommand({
880
906
  await ctx.page.mouse.move(p.x, p.y, { steps: p.steps || 1 });
881
907
  break;
882
908
  case "down":
909
+ await ctx.page.mouse.move(p.x, p.y);
883
910
  await ctx.page.mouse.down({ button });
884
911
  break;
885
912
  case "up":
913
+ await ctx.page.mouse.move(p.x, p.y);
886
914
  await ctx.page.mouse.up({ button });
887
915
  break;
888
916
  case "click":
@@ -896,28 +924,441 @@ var mouseCommand = registerCommand({
896
924
  }
897
925
  });
898
926
 
899
- // src/commands/evaluate.ts
927
+ // src/commands/find-visual.ts
900
928
  import { z as z7 } from "zod";
901
- import { ok as ok7, normalizeTips as normalizeTips2 } from "@dyyz1993/xcli-core";
929
+ import { ok as ok7, fail as fail2 } from "@dyyz1993/xcli-core";
930
+ import { readFileSync, existsSync } from "fs";
931
+ import { homedir as homedir2 } from "os";
932
+ import { join as join2 } from "path";
933
+ function loadVLMCredentials() {
934
+ const candidates = [
935
+ process.env.XBROWSER_VLM_CONFIG,
936
+ join2(homedir2(), ".zcode", "v2", "config.json")
937
+ ].filter(Boolean);
938
+ for (const file of candidates) {
939
+ try {
940
+ if (!existsSync(file)) continue;
941
+ const cfg = JSON.parse(readFileSync(file, "utf8"));
942
+ const provider = Object.values(cfg.provider ?? {})[0];
943
+ if (provider?.options?.apiKey && provider.options.baseURL) {
944
+ const models = Object.keys(provider.models ?? {});
945
+ const vl = models.find((m) => /vl|vision/i.test(m));
946
+ const flash = models.find((m) => /flash/i.test(m) && /5\.?3/i.test(m));
947
+ return {
948
+ apiKey: provider.options.apiKey,
949
+ baseURL: provider.options.baseURL.replace(/\/$/, ""),
950
+ model: process.env.XBROWSER_VLM_MODEL || vl || flash || models.find((m) => /5\.[23]/.test(m)) || models[0]
951
+ };
952
+ }
953
+ } catch {
954
+ }
955
+ }
956
+ return null;
957
+ }
958
+ async function locateByVLM(creds, imageBase64, target, feedback) {
959
+ 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}"""
960
+
961
+ \u4E25\u683C\u53EA\u8F93\u51FA\u4E00\u4E2A JSON \u5BF9\u8C61\uFF08\u65E0 markdown\u3001\u65E0\u89E3\u91CA\uFF09\uFF1A
962
+ {"label":"<\u5143\u7D20\u540D>","box":{"x":<\u5DE6\u4E0Ax>,"y":<\u5DE6\u4E0Ay>,"width":<\u5BBD>,"height":<\u9AD8>},"confidence":"high|medium|low"}
963
+ \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
964
+ ${feedback ? `\u6CE8\u610F\uFF1A${feedback}` : ""}`;
965
+ const resp = await fetch(creds.baseURL + "/v1/messages", {
966
+ method: "POST",
967
+ headers: { "content-type": "application/json", "x-api-key": creds.apiKey, "anthropic-version": "2023-06-01" },
968
+ body: JSON.stringify({
969
+ model: creds.model,
970
+ max_tokens: 2e3,
971
+ // thinking 模型的思考块也计入配额,300 会在 text 前截断
972
+ messages: [{
973
+ role: "user",
974
+ content: [
975
+ { type: "image", source: { type: "base64", media_type: "image/png", data: imageBase64 } },
976
+ { type: "text", text: prompt }
977
+ ]
978
+ }]
979
+ }),
980
+ signal: AbortSignal.timeout(6e4)
981
+ });
982
+ if (!resp.ok) throw new Error(`VLM HTTP ${resp.status}`);
983
+ const data = await resp.json();
984
+ const text = (data.content ?? []).filter((c) => c.type === "text").map((c) => c.text ?? "").join("");
985
+ const m = text.match(/\{[\s\S]*\}/);
986
+ if (!m) {
987
+ console.error("[find-visual] VLM raw (no JSON):", text.substring(0, 200));
988
+ return null;
989
+ }
990
+ const hit = JSON.parse(m[0]);
991
+ if (!hit.label || !hit.box) return null;
992
+ return hit;
993
+ }
994
+ var findVisualCommand = registerCommand({
995
+ name: "find-visual",
996
+ description: "Visual element location: screenshot \u2192 VLM \u2192 page coordinates (optionally click)",
997
+ scope: "page",
998
+ parameters: z7.object({
999
+ 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'),
1000
+ click: z7.boolean().optional().describe("\u627E\u5230\u540E\u7ACB\u5373\u70B9\u51FB\uFF08\u771F\u5B9E\u9F20\u6807\u4E8B\u4EF6\uFF09"),
1001
+ threshold: z7.coerce.number().optional().describe("\u7F6E\u4FE1\u5EA6\u9608\u503C\uFF0C\u4F4E\u4E8E\u5219\u62A5 not-found\uFF08\u9ED8\u8BA4 0\uFF09"),
1002
+ 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")
1003
+ }),
1004
+ result: z7.object({
1005
+ found: z7.boolean(),
1006
+ element: z7.string(),
1007
+ box: z7.unknown().optional(),
1008
+ center: z7.unknown().optional(),
1009
+ confidence: z7.string().optional(),
1010
+ model: z7.string().optional(),
1011
+ clicked: z7.boolean().optional(),
1012
+ screenshot: z7.string().optional()
1013
+ }),
1014
+ handler: async (p, ctx) => {
1015
+ const page = ctx.page;
1016
+ const creds = loadVLMCredentials();
1017
+ if (!creds) {
1018
+ 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", [
1019
+ "\u4E5F\u53EF\u7528 XBROWSER_VLM_MODEL \u6307\u5B9A\u6A21\u578B\uFF08\u9ED8\u8BA4\u81EA\u52A8\u9009 VL/\u65D7\u8230\u6A21\u578B\uFF09"
1020
+ ]);
1021
+ }
1022
+ const buffer = await page.screenshot({ type: "png" });
1023
+ const imageBase64 = buffer.toString("base64");
1024
+ const imgW = buffer.readUInt32BE(16);
1025
+ const imgH = buffer.readUInt32BE(20);
1026
+ let hit = null;
1027
+ let feedback = "";
1028
+ try {
1029
+ for (let attempt = 0; attempt < 3 && !hit; attempt++) {
1030
+ const candidate = await locateByVLM(creds, imageBase64, p.element, feedback);
1031
+ if (!candidate) break;
1032
+ const b = candidate.box;
1033
+ const inBounds = b.x >= 0 && b.y >= 0 && b.x + b.width <= imgW + 2 && b.y + b.height <= imgH + 2;
1034
+ const sane = b.width > 2 && b.height > 2;
1035
+ if (inBounds && sane) {
1036
+ hit = candidate;
1037
+ break;
1038
+ }
1039
+ 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`;
1040
+ }
1041
+ } catch (e) {
1042
+ return fail2(`VLM \u8C03\u7528\u5931\u8D25: ${e instanceof Error ? e.message : String(e)}`);
1043
+ }
1044
+ if (!hit) {
1045
+ return ok7({ found: false, element: p.element, model: creds.model }, [
1046
+ `\u622A\u56FE\u4E2D\u672A\u627E\u5230: ${p.element}`
1047
+ ]);
1048
+ }
1049
+ if (p.zoom !== false && hit) {
1050
+ try {
1051
+ const pad = Math.max(20, Math.round(Math.max(hit.box.width, hit.box.height) * 0.5));
1052
+ const clip = {
1053
+ x: Math.max(0, hit.box.x - pad),
1054
+ y: Math.max(0, hit.box.y - pad),
1055
+ width: Math.min(imgW - Math.max(0, hit.box.x - pad), hit.box.width + pad * 2),
1056
+ height: Math.min(imgH - Math.max(0, hit.box.y - pad), hit.box.height + pad * 2)
1057
+ };
1058
+ if (clip.width > 8 && clip.height > 8) {
1059
+ const zoomBuf = await page.screenshot({ type: "png", clip });
1060
+ const zoomB64 = zoomBuf.toString("base64");
1061
+ const zoomW = zoomBuf.readUInt32BE(16);
1062
+ const zoomH = zoomBuf.readUInt32BE(20);
1063
+ const refined = await locateByVLM(creds, zoomB64, p.element);
1064
+ if (refined) {
1065
+ const rb = refined.box;
1066
+ 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;
1067
+ if (inZoom) {
1068
+ hit = {
1069
+ label: refined.label || hit.label,
1070
+ box: {
1071
+ x: clip.x + rb.x,
1072
+ y: clip.y + rb.y,
1073
+ width: rb.width,
1074
+ height: rb.height
1075
+ },
1076
+ confidence: refined.confidence === "low" ? hit.confidence : refined.confidence
1077
+ };
1078
+ }
1079
+ }
1080
+ }
1081
+ } catch {
1082
+ }
1083
+ }
1084
+ if (p.threshold !== void 0) {
1085
+ const rank = { high: 3, medium: 2, low: 1 };
1086
+ const thresholdName = p.threshold >= 3 ? "high" : p.threshold >= 2 ? "medium" : "low";
1087
+ if ((rank[hit.confidence ?? "low"] ?? 1) < (rank[thresholdName] ?? 1)) {
1088
+ return ok7({ found: false, element: p.element, confidence: hit.confidence, box: hit.box, model: creds.model }, [
1089
+ `\u7F6E\u4FE1\u5EA6 ${hit.confidence} \u4F4E\u4E8E\u9608\u503C ${p.threshold}`
1090
+ ]);
1091
+ }
1092
+ }
1093
+ const viewport = await page.evaluate("JSON.stringify({w: window.innerWidth, h: window.innerHeight})").then((v) => JSON.parse(String(v))).catch(() => ({ w: 1280, h: 800 }));
1094
+ const pngW = buffer.readUInt32BE(16);
1095
+ const pngH = buffer.readUInt32BE(20);
1096
+ const scaleX = viewport.w / pngW;
1097
+ const scaleY = viewport.h / pngH;
1098
+ const center = {
1099
+ x: Math.round((hit.box.x + hit.box.width / 2) * scaleX),
1100
+ y: Math.round((hit.box.y + hit.box.height / 2) * scaleY)
1101
+ };
1102
+ let clicked = false;
1103
+ if (p.click) {
1104
+ await page.mouse.click(center.x, center.y, { stealth: true });
1105
+ clicked = true;
1106
+ }
1107
+ return ok7({
1108
+ found: true,
1109
+ element: p.element,
1110
+ box: hit.box,
1111
+ center,
1112
+ confidence: hit.confidence,
1113
+ model: creds.model,
1114
+ clicked,
1115
+ screenshot: `data:image/png;base64,${imageBase64.substring(0, 64)}... (${Math.round(imageBase64.length / 1024)}KB)`
1116
+ }, [
1117
+ 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`
1118
+ ]);
1119
+ }
1120
+ });
1121
+
1122
+ // src/commands/vision-task.ts
1123
+ import { z as z8 } from "zod";
1124
+ import { ok as ok8, fail as fail3 } from "@dyyz1993/xcli-core";
1125
+ import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
1126
+ import { homedir as homedir3 } from "os";
1127
+ import { join as join3 } from "path";
1128
+ function loadVLMCredentials2() {
1129
+ const candidates = [
1130
+ process.env.XBROWSER_VLM_CONFIG,
1131
+ join3(homedir3(), ".zcode", "v2", "config.json")
1132
+ ].filter(Boolean);
1133
+ for (const file of candidates) {
1134
+ try {
1135
+ if (!existsSync2(file)) continue;
1136
+ const cfg = JSON.parse(readFileSync2(file, "utf8"));
1137
+ const provider = Object.values(cfg.provider ?? {})[0];
1138
+ if (provider?.options?.apiKey && provider.options.baseURL) {
1139
+ const models = Object.keys(provider.models ?? {});
1140
+ const vl = models.find((m) => /vl|vision/i.test(m));
1141
+ const flash = models.find((m) => /flash/i.test(m) && /5\.?3/i.test(m));
1142
+ return {
1143
+ apiKey: provider.options.apiKey,
1144
+ baseURL: provider.options.baseURL.replace(/\/$/, ""),
1145
+ model: process.env.XBROWSER_VLM_MODEL || vl || flash || models.find((m) => /5\.[23]/.test(m)) || models[0]
1146
+ };
1147
+ }
1148
+ } catch {
1149
+ }
1150
+ }
1151
+ return null;
1152
+ }
1153
+ async function vlmAsk(creds, content, maxTokens = 2e3) {
1154
+ const resp = await fetch(creds.baseURL + "/v1/messages", {
1155
+ method: "POST",
1156
+ headers: { "content-type": "application/json", "x-api-key": creds.apiKey, "anthropic-version": "2023-06-01" },
1157
+ body: JSON.stringify({ model: creds.model, max_tokens: maxTokens, messages: [{ role: "user", content }] }),
1158
+ signal: AbortSignal.timeout(9e4)
1159
+ });
1160
+ if (!resp.ok) throw new Error(`VLM HTTP ${resp.status}`);
1161
+ const data = await resp.json();
1162
+ return (data.content ?? []).filter((c) => c.type === "text").map((c) => c.text ?? "").join("");
1163
+ }
1164
+ async function vlmAskRetry(creds, content, maxTokens = 2e3) {
1165
+ let last = "";
1166
+ for (let i = 0; i < 2; i++) {
1167
+ last = await vlmAsk(creds, content, maxTokens);
1168
+ if (last.trim()) return last;
1169
+ await new Promise((r) => setTimeout(r, 1200));
1170
+ }
1171
+ return last;
1172
+ }
1173
+ function pngDims(b64) {
1174
+ const buf = Buffer.from(b64, "base64");
1175
+ return { w: buf.readUInt32BE(16), h: buf.readUInt32BE(20) };
1176
+ }
1177
+ function parseDecision(text) {
1178
+ const m = text.match(/\{[\s\S]*\}/);
1179
+ if (!m) return null;
1180
+ try {
1181
+ const j = JSON.parse(m[0]);
1182
+ if (!j.action || !j.action.type) return null;
1183
+ return j;
1184
+ } catch {
1185
+ return null;
1186
+ }
1187
+ }
1188
+ 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
1189
+
1190
+ \u4E25\u683C\u53EA\u8F93\u51FA\u4E00\u4E2A JSON\uFF08\u65E0 markdown\uFF09\uFF1A
1191
+ {"thought":"<\u7B80\u77ED\u63A8\u7406>","action":{"type":"click","x":<int>,"y":<int>}
1192
+ \u6216 {"type":"type","text":"<\u8981\u8F93\u5165\u7684\u6587\u5B57>"}\uFF08\u8F93\u5165\u524D\u987B\u5DF2\u70B9\u51FB\u805A\u7126\u8F93\u5165\u6846\uFF09
1193
+ \u6216 {"type":"key","key":"Enter"}\uFF08key \u540D\uFF1AEnter/Tab/Escape/ArrowDown\u2026\uFF09
1194
+ \u6216 {"type":"scroll","direction":"down","amount":<px>}
1195
+ \u6216 {"type":"done","summary":"<\u4EFB\u52A1\u5B8C\u6210\u6458\u8981>"}
1196
+ \u6216 {"type":"stuck","reason":"<\u65E0\u6CD5\u7EE7\u7EED\u7684\u539F\u56E0>"}}
1197
+
1198
+ \u5750\u6807\u4EE5\u622A\u56FE\u5DE6\u4E0A\u89D2\u4E3A\u539F\u70B9\u3001\u50CF\u7D20\u4E3A\u5355\u4F4D\u3002\u89C4\u5219\uFF1A
1199
+ 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
1200
+ 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
1201
+ 3. \u4EFB\u52A1\u76EE\u6807\u8FBE\u6210\u7ACB\u5373\u8F93\u51FA done\uFF1B5 \u6B65\u65E0\u8FDB\u5C55\u8F93\u51FA stuck`;
1202
+ var visionTaskCommand = registerCommand({
1203
+ name: "vision-task",
1204
+ description: "Vision-loop agent: screenshot \u2192 VLM decide \u2192 CDP act \u2192 verify, until done",
1205
+ scope: "page",
1206
+ parameters: z8.object({
1207
+ task: z8.string().describe('\u81EA\u7136\u8BED\u8A00\u4EFB\u52A1\uFF0C\u5982 "\u641C\u7D22 xbrowser \u5E76\u6253\u5F00\u7B2C\u4E00\u7BC7\u6587\u7AE0"'),
1208
+ maxSteps: z8.coerce.number().optional().describe("\u6700\u5927\u6B65\u6570\uFF08\u9ED8\u8BA4 12\uFF09"),
1209
+ settleMs: z8.coerce.number().optional().describe("\u6BCF\u6B65\u540E\u7B49\u5F85\u9875\u9762\u7A33\u5B9A\u7684\u6BEB\u79D2\u6570\uFF08\u9ED8\u8BA4 1500\uFF09")
1210
+ }),
1211
+ result: z8.object({
1212
+ done: z8.boolean(),
1213
+ steps: z8.unknown().optional(),
1214
+ summary: z8.string().optional(),
1215
+ model: z8.string().optional()
1216
+ }),
1217
+ handler: async (p, ctx) => {
1218
+ let page = ctx.page;
1219
+ const creds = loadVLMCredentials2();
1220
+ if (!creds) {
1221
+ return fail3("\u672A\u627E\u5230 VLM \u51ED\u636E\uFF1A\u9700\u8981 ~/.zcode/v2/config.json \u6216 XBROWSER_VLM_CONFIG");
1222
+ }
1223
+ const maxSteps = p.maxSteps ?? 12;
1224
+ const settleMs = p.settleMs ?? 1500;
1225
+ const steps = [];
1226
+ const history = [];
1227
+ let lastUrl = page.url();
1228
+ for (let step = 1; step <= maxSteps; step++) {
1229
+ if (page.url() === "about:blank") {
1230
+ try {
1231
+ const ctxObj = page.context?.();
1232
+ const pages = ctxObj?.pages?.() || [];
1233
+ let real;
1234
+ for (let i = pages.length - 1; i >= 0; i--) {
1235
+ const u = pages[i]?.url?.() ?? "";
1236
+ if (u && u !== "about:blank") {
1237
+ real = pages[i];
1238
+ break;
1239
+ }
1240
+ }
1241
+ if (real) {
1242
+ await real.bringToFront().catch(() => {
1243
+ });
1244
+ page = real;
1245
+ }
1246
+ } catch {
1247
+ }
1248
+ }
1249
+ const buffer = await page.screenshot({ type: "png" });
1250
+ const b64 = buffer.toString("base64");
1251
+ const { w, h } = pngDims(b64);
1252
+ const obsText = [
1253
+ `\u4EFB\u52A1\uFF1A${p.task}`,
1254
+ `\u622A\u56FE\u5C3A\u5BF8\uFF1A${w}x${h}\uFF08\u5750\u6807\u539F\u70B9\uFF1A\u5DE6\u4E0A\u89D2\uFF09`,
1255
+ `\u5F53\u524D URL\uFF1A${page.url()}`,
1256
+ history.length ? `\u5DF2\u6267\u884C\u52A8\u4F5C\uFF1A
1257
+ ${history.slice(-6).join("\n")}` : "\u5C1A\u65E0\u52A8\u4F5C\u3002"
1258
+ ].join("\n");
1259
+ const reply = await vlmAskRetry(creds, [
1260
+ { type: "image", source: { type: "base64", media_type: "image/png", data: b64 } },
1261
+ { type: "text", text: `${SYSTEM_PROMPT}
1262
+
1263
+ ${obsText}` }
1264
+ ]);
1265
+ const decision = parseDecision(reply);
1266
+ if (!decision) {
1267
+ steps.push({ n: step, thought: "(unparseable)", action: "stop", ts: Date.now() });
1268
+ return ok8({ done: false, steps, summary: `\u7B2C ${step} \u6B65 VLM \u8F93\u51FA\u65E0\u6CD5\u89E3\u6790: ${reply.substring(0, 120)}`, model: creds.model });
1269
+ }
1270
+ const a = decision.action;
1271
+ let acted = "";
1272
+ try {
1273
+ if (a.type === "done") {
1274
+ steps.push({ n: step, thought: decision.thought, action: "done", ts: Date.now() });
1275
+ return ok8({ done: true, steps, summary: a.summary, model: creds.model });
1276
+ }
1277
+ if (a.type === "stuck") {
1278
+ steps.push({ n: step, thought: decision.thought, action: "stuck", ts: Date.now() });
1279
+ return ok8({ done: false, steps, summary: `stuck: ${a.reason}`, model: creds.model });
1280
+ }
1281
+ if (a.type === "click") {
1282
+ const cx = Math.max(2, Math.min(w - 2, Math.round(a.x)));
1283
+ const cy = Math.max(2, Math.min(h - 2, Math.round(a.y)));
1284
+ await page.mouse.click(cx, cy, { stealth: true });
1285
+ acted = `click(${cx},${cy})`;
1286
+ } else if (a.type === "type") {
1287
+ await page.keyboard.type(a.text, { stealth: true });
1288
+ acted = `type("${a.text.substring(0, 30)}")`;
1289
+ } else if (a.type === "key") {
1290
+ await page.keyboard.press(a.key);
1291
+ acted = `key(${a.key})`;
1292
+ } else if (a.type === "scroll") {
1293
+ await page.mouse.wheel(0, a.direction === "down" ? a.amount : -a.amount);
1294
+ acted = `scroll(${a.direction},${a.amount})`;
1295
+ }
1296
+ } catch (e) {
1297
+ acted = `${a.type} FAILED: ${e instanceof Error ? e.message : String(e).substring(0, 80)}`;
1298
+ }
1299
+ steps.push({ n: step, thought: decision.thought, action: acted, ts: Date.now() });
1300
+ history.push(`${step}. ${acted} \u2014 ${decision.thought.substring(0, 60)}`);
1301
+ await new Promise((r) => setTimeout(r, settleMs));
1302
+ if (a.type === "click") {
1303
+ try {
1304
+ const ctxObj = page.context?.();
1305
+ const pages = ctxObj?.pages?.() || [];
1306
+ for (let i = pages.length - 1; i >= 0; i--) {
1307
+ const cand = pages[i];
1308
+ if (!cand || cand === page) continue;
1309
+ const u = cand.url?.() ?? "";
1310
+ if (!u || u === "about:blank") continue;
1311
+ if (u === page.url()) break;
1312
+ await cand.bringToFront().catch(() => {
1313
+ });
1314
+ page = cand;
1315
+ break;
1316
+ }
1317
+ } catch {
1318
+ }
1319
+ }
1320
+ const urlNow = page.url();
1321
+ if (urlNow !== lastUrl) {
1322
+ history.push(` [\u9875\u9762\u8DF3\u8F6C \u2192 ${urlNow.substring(0, 80)}]`);
1323
+ lastUrl = urlNow;
1324
+ }
1325
+ }
1326
+ return ok8({ done: false, steps, summary: `\u5DF2\u8FBE\u6700\u5927\u6B65\u6570 ${maxSteps}`, model: creds.model });
1327
+ }
1328
+ });
1329
+
1330
+ // src/commands/evaluate.ts
1331
+ import { z as z9 } from "zod";
1332
+ import { ok as ok9, normalizeTips as normalizeTips2 } from "@dyyz1993/xcli-core";
902
1333
  var evaluateCommand = registerCommand({
903
1334
  name: "eval",
904
- description: "Evaluate JavaScript expression in the browser",
1335
+ description: "Evaluate JavaScript expression in the browser (--frame <url-substring> \u53EF\u5B9A\u5411\u5230 iframe \u4E0A\u4E0B\u6587\uFF0C\u8DE8\u57DF iframe \u4EA6\u53EF)",
905
1336
  scope: "page",
906
- parameters: z7.object({
907
- expression: z7.string()
1337
+ parameters: z9.object({
1338
+ expression: z9.string().optional(),
1339
+ 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"),
1340
+ // Internal: base64url-encoded script set by the router for `eval <js...>`,
1341
+ // so multiline/semicolon JS survives chain parsing (which splits on ';'
1342
+ // and whitespace). Not intended for direct CLI use.
1343
+ "script-b64": z9.string().optional().describe("Internal: base64url script set by the CLI router; do not pass directly")
908
1344
  }),
909
1345
  handler: async (p, ctx) => {
1346
+ const rawScript = p["script-b64"];
1347
+ const expression = rawScript ? Buffer.from(rawScript, "base64url").toString("utf8") : p.expression;
1348
+ if (!expression || !expression.trim()) {
1349
+ return { ok: false, error: "eval: expression is required" };
1350
+ }
910
1351
  const engine = createRuleEngine();
911
1352
  engine.start();
912
1353
  const decision = engine.evaluate({
913
1354
  method: "Runtime.evaluate",
914
- params: { expression: p.expression },
1355
+ params: { expression },
915
1356
  sessionId: "eval-check",
916
1357
  direction: "client\u2192browser"
917
1358
  });
918
1359
  engine.stop();
919
- const result = await ctx.page.evaluate(p.expression);
920
- const response = ok7({ result });
1360
+ const result = p.frame ? await ctx.page.evaluateInFrame(p.frame, expression) : await ctx.page.evaluate(expression);
1361
+ const response = ok9({ result });
921
1362
  if (decision && decision.severity === "danger") {
922
1363
  response.tips = normalizeTips2([`${decision.suggestion}`]);
923
1364
  }
@@ -925,37 +1366,65 @@ var evaluateCommand = registerCommand({
925
1366
  }
926
1367
  });
927
1368
 
1369
+ // src/commands/ua.ts
1370
+ import { z as z10 } from "zod";
1371
+ import { ok as ok10 } from "@dyyz1993/xcli-core";
1372
+ function uaHookScript(ua) {
1373
+ const uaJson = JSON.stringify(ua);
1374
+ const appVersion = JSON.stringify(ua.replace(/^Mozilla\//, ""));
1375
+ 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){}`;
1376
+ }
1377
+ var uaCommand = registerCommand({
1378
+ name: "ua",
1379
+ description: "Get or override the session User-Agent (anti-bot UA detection countermeasure)",
1380
+ scope: "page",
1381
+ parameters: z10.object({
1382
+ 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')
1383
+ }),
1384
+ handler: async (p, ctx) => {
1385
+ if (!p.value) {
1386
+ const current = await ctx.page.evaluate("navigator.userAgent");
1387
+ return ok10({ userAgent: current });
1388
+ }
1389
+ 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;
1390
+ const script = uaHookScript(ua);
1391
+ await ctx.page.addInitScript(script);
1392
+ await ctx.page.evaluate(`(function(){${script}})()`);
1393
+ return ok10({ userAgent: ua, mode: "init-script" });
1394
+ }
1395
+ });
1396
+
928
1397
  // src/commands/storage.ts
929
- import { z as z8 } from "zod";
930
- import { ok as ok8, fail as fail2 } from "@dyyz1993/xcli-core";
1398
+ import { z as z11 } from "zod";
1399
+ import { ok as ok11, fail as fail4 } from "@dyyz1993/xcli-core";
931
1400
  var getCookiesCommand = registerCommand({
932
1401
  name: "get-cookies",
933
1402
  description: "Get all cookies for the current page",
934
1403
  scope: "page",
935
- result: z8.object({
936
- cookies: z8.array(z8.record(z8.unknown()))
1404
+ result: z11.object({
1405
+ cookies: z11.array(z11.record(z11.unknown()))
937
1406
  }),
938
1407
  handler: async (_p, ctx) => {
939
1408
  const cookies = await ctx.browserContext.cookies();
940
- return ok8({ cookies });
1409
+ return ok11({ cookies });
941
1410
  }
942
1411
  });
943
1412
  var setCookieCommand = registerCommand({
944
1413
  name: "set-cookie",
945
1414
  description: "Set a cookie",
946
1415
  scope: "page",
947
- parameters: z8.object({
948
- name: z8.coerce.string(),
949
- value: z8.coerce.string(),
950
- domain: z8.coerce.string().optional(),
951
- path: z8.coerce.string().optional(),
952
- url: z8.string().optional().describe("Cookie URL (alternative to domain)"),
953
- expires: z8.number().optional(),
954
- httpOnly: z8.boolean().optional(),
955
- secure: z8.boolean().optional(),
956
- sameSite: z8.enum(["Strict", "Lax", "None"]).optional()
1416
+ parameters: z11.object({
1417
+ name: z11.coerce.string(),
1418
+ value: z11.coerce.string(),
1419
+ domain: z11.coerce.string().optional(),
1420
+ path: z11.coerce.string().optional(),
1421
+ url: z11.string().optional().describe("Cookie URL (alternative to domain)"),
1422
+ expires: z11.number().optional(),
1423
+ httpOnly: z11.boolean().optional(),
1424
+ secure: z11.boolean().optional(),
1425
+ sameSite: z11.enum(["Strict", "Lax", "None"]).optional()
957
1426
  }),
958
- result: z8.object({ name: z8.string() }),
1427
+ result: z11.object({ name: z11.string() }),
959
1428
  handler: async (p, ctx) => {
960
1429
  const cookie = { ...p };
961
1430
  if (!cookie.domain && !cookie.url) {
@@ -970,38 +1439,38 @@ var setCookieCommand = registerCommand({
970
1439
  }
971
1440
  }
972
1441
  if (!cookie.domain && !cookie.url) {
973
- return fail2("set-cookie requires --domain or --url, or a non-blank page URL to infer from");
1442
+ return fail4("set-cookie requires --domain or --url, or a non-blank page URL to infer from");
974
1443
  }
975
1444
  await ctx.browserContext.addCookies([cookie]);
976
- return ok8({ name: p.name });
1445
+ return ok11({ name: p.name });
977
1446
  }
978
1447
  });
979
1448
  var clearCookiesCommand = registerCommand({
980
1449
  name: "clear-cookies",
981
1450
  description: "Clear all cookies",
982
1451
  scope: "page",
983
- result: z8.object({ cleared: z8.boolean() }),
1452
+ result: z11.object({ cleared: z11.boolean() }),
984
1453
  handler: async (_p, ctx) => {
985
1454
  await ctx.browserContext.clearCookies();
986
- return ok8({ cleared: true });
1455
+ return ok11({ cleared: true });
987
1456
  }
988
1457
  });
989
1458
  var getLocalStorageCommand = registerCommand({
990
1459
  name: "get-local-storage",
991
1460
  description: "Get localStorage entries",
992
1461
  scope: "page",
993
- parameters: z8.object({
994
- key: z8.string().optional()
1462
+ parameters: z11.object({
1463
+ key: z11.string().optional()
995
1464
  }),
996
- result: z8.union([
997
- z8.object({ key: z8.string(), value: z8.string().nullable() }),
998
- z8.object({ data: z8.record(z8.string()) })
1465
+ result: z11.union([
1466
+ z11.object({ key: z11.string(), value: z11.string().nullable() }),
1467
+ z11.object({ data: z11.record(z11.string()) })
999
1468
  ]),
1000
1469
  handler: async (p, ctx) => {
1001
1470
  try {
1002
1471
  if (p.key) {
1003
1472
  const value = await ctx.page.evaluate((k) => localStorage.getItem(k), p.key);
1004
- return ok8({ key: p.key, value });
1473
+ return ok11({ key: p.key, value });
1005
1474
  }
1006
1475
  const data = await ctx.page.evaluate(() => {
1007
1476
  const entries = {};
@@ -1011,9 +1480,9 @@ var getLocalStorageCommand = registerCommand({
1011
1480
  }
1012
1481
  return entries;
1013
1482
  });
1014
- return ok8({ data });
1483
+ return ok11({ data });
1015
1484
  } catch (e) {
1016
- return fail2(`localStorage not accessible on this page: ${e instanceof Error ? e.message : String(e)}`);
1485
+ return fail4(`localStorage not accessible on this page: ${e instanceof Error ? e.message : String(e)}`);
1017
1486
  }
1018
1487
  }
1019
1488
  });
@@ -1021,11 +1490,11 @@ var setLocalStorageCommand = registerCommand({
1021
1490
  name: "set-local-storage",
1022
1491
  description: "Set a localStorage entry",
1023
1492
  scope: "page",
1024
- parameters: z8.object({
1025
- key: z8.string(),
1026
- value: z8.string()
1493
+ parameters: z11.object({
1494
+ key: z11.string(),
1495
+ value: z11.string()
1027
1496
  }),
1028
- result: z8.object({ key: z8.string() }),
1497
+ result: z11.object({ key: z11.string() }),
1029
1498
  handler: async (p, ctx) => {
1030
1499
  try {
1031
1500
  await ctx.page.evaluate(
@@ -1034,9 +1503,9 @@ var setLocalStorageCommand = registerCommand({
1034
1503
  },
1035
1504
  { key: p.key, value: p.value }
1036
1505
  );
1037
- return ok8({ key: p.key });
1506
+ return ok11({ key: p.key });
1038
1507
  } catch (e) {
1039
- return fail2(`localStorage not accessible on this page: ${e instanceof Error ? e.message : String(e)}`);
1508
+ return fail4(`localStorage not accessible on this page: ${e instanceof Error ? e.message : String(e)}`);
1040
1509
  }
1041
1510
  }
1042
1511
  });
@@ -1044,26 +1513,24 @@ var clearLocalStorageCommand = registerCommand({
1044
1513
  name: "clear-local-storage",
1045
1514
  description: "Clear all localStorage entries",
1046
1515
  scope: "page",
1047
- result: z8.object({ cleared: z8.boolean() }),
1516
+ result: z11.object({ cleared: z11.boolean() }),
1048
1517
  handler: async (_p, ctx) => {
1049
1518
  try {
1050
1519
  await ctx.page.evaluate(() => localStorage.clear());
1051
- return ok8({ cleared: true });
1520
+ return ok11({ cleared: true });
1052
1521
  } catch (e) {
1053
- return fail2(`localStorage not accessible on this page: ${e instanceof Error ? e.message : String(e)}`);
1522
+ return fail4(`localStorage not accessible on this page: ${e instanceof Error ? e.message : String(e)}`);
1054
1523
  }
1055
1524
  }
1056
1525
  });
1057
1526
 
1058
1527
  // src/commands/screenshot.ts
1059
- import { z as z9 } from "zod";
1060
- import { ok as ok9, fail as fail3 } from "@dyyz1993/xcli-core";
1528
+ import { z as z12 } from "zod";
1529
+ import { ok as ok12, fail as fail5 } from "@dyyz1993/xcli-core";
1061
1530
  import { writeFileSync, mkdirSync } from "fs";
1062
- import { dirname, join as join2 } from "path";
1063
- import { homedir as homedir2 } from "os";
1064
- var SCREENSHOTS_DIR = join2(homedir2(), ".xbrowser", "screenshots");
1531
+ import { dirname, join as join4 } from "path";
1065
1532
  function ensureScreenshotsDir() {
1066
- mkdirSync(SCREENSHOTS_DIR, { recursive: true });
1533
+ mkdirSync(resolveScreenshotsDir(), { recursive: true });
1067
1534
  }
1068
1535
  function ensureParentDir(filePath) {
1069
1536
  const dir = dirname(filePath);
@@ -1079,30 +1546,30 @@ function generateScreenshotPath(format) {
1079
1546
  const timestamp = Date.now();
1080
1547
  const random = Math.random().toString(36).slice(2, 8);
1081
1548
  const ext = format === "jpeg" ? "jpg" : "png";
1082
- return join2(SCREENSHOTS_DIR, `screenshot-${timestamp}-${random}.${ext}`);
1549
+ return join4(resolveScreenshotsDir(), `screenshot-${timestamp}-${random}.${ext}`);
1083
1550
  }
1084
1551
  var screenshotCommand = registerCommand({
1085
1552
  name: "screenshot",
1086
- description: "Take a screenshot of the page or element",
1553
+ 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>)",
1087
1554
  scope: "page",
1088
1555
  selectorParams: ["selector"],
1089
- parameters: z9.object({
1090
- selector: z9.string().optional(),
1091
- type: z9.enum(["png", "jpeg"]).optional(),
1092
- fullPage: z9.boolean().optional(),
1093
- output: z9.string().optional(),
1094
- base64: z9.boolean().optional().describe("Return base64 data instead of file path")
1556
+ parameters: z12.object({
1557
+ selector: z12.string().optional(),
1558
+ type: z12.enum(["png", "jpeg"]).optional(),
1559
+ fullPage: z12.boolean().optional(),
1560
+ output: z12.string().optional(),
1561
+ base64: z12.boolean().optional().describe("Return base64 data instead of file path")
1095
1562
  }),
1096
- result: z9.union([
1097
- z9.object({
1098
- data: z9.string(),
1099
- format: z9.string(),
1100
- size: z9.number()
1563
+ result: z12.union([
1564
+ z12.object({
1565
+ data: z12.string(),
1566
+ format: z12.string(),
1567
+ size: z12.number()
1101
1568
  }),
1102
- z9.object({
1103
- output: z9.string(),
1104
- format: z9.string(),
1105
- size: z9.number()
1569
+ z12.object({
1570
+ output: z12.string(),
1571
+ format: z12.string(),
1572
+ size: z12.number()
1106
1573
  })
1107
1574
  ]),
1108
1575
  handler: async (p, ctx) => {
@@ -1120,21 +1587,21 @@ var screenshotCommand = registerCommand({
1120
1587
  if (p.output) {
1121
1588
  const dirErr = ensureParentDir(p.output);
1122
1589
  if (dirErr) {
1123
- return fail3(`Cannot create directory for --output "${p.output}": ${dirErr}`);
1590
+ return fail5(`Cannot create directory for --output "${p.output}": ${dirErr}`);
1124
1591
  }
1125
1592
  try {
1126
1593
  writeFileSync(p.output, buffer, "binary");
1127
1594
  } catch (err) {
1128
- return fail3(`Failed to write screenshot to "${p.output}": ${err instanceof Error ? err.message : String(err)}`);
1595
+ return fail5(`Failed to write screenshot to "${p.output}": ${err instanceof Error ? err.message : String(err)}`);
1129
1596
  }
1130
- return ok9({
1597
+ return ok12({
1131
1598
  output: p.output,
1132
1599
  format,
1133
1600
  size: buffer.length
1134
1601
  });
1135
1602
  }
1136
1603
  if (p.base64) {
1137
- return ok9({
1604
+ return ok12({
1138
1605
  data: buffer.toString("base64"),
1139
1606
  format,
1140
1607
  size: buffer.length
@@ -1143,7 +1610,7 @@ var screenshotCommand = registerCommand({
1143
1610
  ensureScreenshotsDir();
1144
1611
  const screenshotPath = generateScreenshotPath(format);
1145
1612
  writeFileSync(screenshotPath, buffer, "binary");
1146
- return ok9({
1613
+ return ok12({
1147
1614
  output: screenshotPath,
1148
1615
  format,
1149
1616
  size: buffer.length
@@ -1152,19 +1619,19 @@ var screenshotCommand = registerCommand({
1152
1619
  });
1153
1620
 
1154
1621
  // src/commands/structure.ts
1155
- import { z as z10 } from "zod";
1156
- import { ok as ok10 } from "@dyyz1993/xcli-core";
1622
+ import { z as z13 } from "zod";
1623
+ import { ok as ok13 } from "@dyyz1993/xcli-core";
1157
1624
  var structureCommand = registerCommand({
1158
1625
  name: "structure",
1159
1626
  description: "Get the DOM structure of the page or an element",
1160
1627
  scope: "page",
1161
1628
  selectorParams: ["selector"],
1162
- parameters: z10.object({
1163
- selector: z10.string().optional(),
1164
- depth: z10.number().optional()
1629
+ parameters: z13.object({
1630
+ selector: z13.string().optional(),
1631
+ depth: z13.number().optional()
1165
1632
  }),
1166
- result: z10.object({
1167
- structure: z10.record(z10.unknown())
1633
+ result: z13.object({
1634
+ structure: z13.record(z13.unknown())
1168
1635
  }),
1169
1636
  handler: async (p, ctx) => {
1170
1637
  const structure = await ctx.page.evaluate(
@@ -1196,30 +1663,30 @@ var structureCommand = registerCommand({
1196
1663
  },
1197
1664
  { sel: p.selector || "body", maxDepth: p.depth || 5 }
1198
1665
  );
1199
- return ok10({ structure });
1666
+ return ok13({ structure });
1200
1667
  }
1201
1668
  });
1202
1669
 
1203
1670
  // src/commands/viewport.ts
1204
- import { z as z11 } from "zod";
1205
- import { ok as ok11 } from "@dyyz1993/xcli-core";
1671
+ import { z as z14 } from "zod";
1672
+ import { ok as ok14 } from "@dyyz1993/xcli-core";
1206
1673
  var setViewportCommand = registerCommand({
1207
1674
  name: "set-viewport",
1208
1675
  description: "Set the viewport size and properties",
1209
1676
  scope: "browser",
1210
- parameters: z11.object({
1211
- width: z11.coerce.number(),
1212
- height: z11.coerce.number(),
1213
- deviceScaleFactor: z11.coerce.number().optional(),
1214
- isMobile: z11.boolean().optional(),
1215
- hasTouch: z11.boolean().optional()
1677
+ parameters: z14.object({
1678
+ width: z14.coerce.number(),
1679
+ height: z14.coerce.number(),
1680
+ deviceScaleFactor: z14.coerce.number().optional(),
1681
+ isMobile: z14.boolean().optional(),
1682
+ hasTouch: z14.boolean().optional()
1216
1683
  }),
1217
- result: z11.object({
1218
- width: z11.number(),
1219
- height: z11.number(),
1220
- deviceScaleFactor: z11.number().optional(),
1221
- isMobile: z11.boolean().optional(),
1222
- hasTouch: z11.boolean().optional()
1684
+ result: z14.object({
1685
+ width: z14.number(),
1686
+ height: z14.number(),
1687
+ deviceScaleFactor: z14.number().optional(),
1688
+ isMobile: z14.boolean().optional(),
1689
+ hasTouch: z14.boolean().optional()
1223
1690
  }),
1224
1691
  handler: async (p, ctx) => {
1225
1692
  const viewport = ctx.page.viewportSize();
@@ -1232,7 +1699,7 @@ var setViewportCommand = registerCommand({
1232
1699
  ...p.isMobile !== void 0 && { isMobile: p.isMobile },
1233
1700
  ...p.hasTouch !== void 0 && { hasTouch: p.hasTouch }
1234
1701
  });
1235
- return ok11({
1702
+ return ok14({
1236
1703
  width,
1237
1704
  height,
1238
1705
  ...p.deviceScaleFactor !== void 0 && { deviceScaleFactor: p.deviceScaleFactor },
@@ -1243,17 +1710,17 @@ var setViewportCommand = registerCommand({
1243
1710
  });
1244
1711
 
1245
1712
  // src/commands/frame.ts
1246
- import { z as z12 } from "zod";
1247
- import { ok as ok12, fail as fail4 } from "@dyyz1993/xcli-core";
1713
+ import { z as z15 } from "zod";
1714
+ import { ok as ok15, fail as fail6 } from "@dyyz1993/xcli-core";
1248
1715
  var framesCommand = registerCommand({
1249
1716
  name: "frames",
1250
1717
  description: "List all frames in the current page",
1251
1718
  scope: "page",
1252
- result: z12.object({
1253
- frames: z12.array(z12.object({
1254
- index: z12.number(),
1255
- name: z12.string().nullable(),
1256
- url: z12.string()
1719
+ result: z15.object({
1720
+ frames: z15.array(z15.object({
1721
+ index: z15.number(),
1722
+ name: z15.string().nullable(),
1723
+ url: z15.string()
1257
1724
  }))
1258
1725
  }),
1259
1726
  handler: async (_p, ctx) => {
@@ -1264,21 +1731,21 @@ var framesCommand = registerCommand({
1264
1731
  name: frame.name(),
1265
1732
  url: frame.url()
1266
1733
  }));
1267
- return ok12({ frames: frameList });
1734
+ return ok15({ frames: frameList });
1268
1735
  }
1269
1736
  });
1270
1737
  var frameCommand = registerCommand({
1271
1738
  name: "frame",
1272
1739
  description: "Get frame info by index or name",
1273
1740
  scope: "page",
1274
- parameters: z12.object({
1275
- index: z12.number().int().min(0).optional(),
1276
- name: z12.string().optional()
1741
+ parameters: z15.object({
1742
+ index: z15.number().int().min(0).optional(),
1743
+ name: z15.string().optional()
1277
1744
  }),
1278
- result: z12.object({
1279
- name: z12.string().nullable(),
1280
- url: z12.string(),
1281
- error: z12.string().optional()
1745
+ result: z15.object({
1746
+ name: z15.string().nullable(),
1747
+ url: z15.string(),
1748
+ error: z15.string().optional()
1282
1749
  }),
1283
1750
  handler: async (p, ctx) => {
1284
1751
  const discover = ctx.page.discoverFrames;
@@ -1289,12 +1756,12 @@ var frameCommand = registerCommand({
1289
1756
  } else if (p.name !== void 0) {
1290
1757
  targetFrame = rawFrames.find((f) => f.name() === p.name);
1291
1758
  } else {
1292
- return fail4("Must provide index or name");
1759
+ return fail6("Must provide index or name");
1293
1760
  }
1294
1761
  if (!targetFrame) {
1295
- return fail4("Frame not found");
1762
+ return fail6("Frame not found");
1296
1763
  }
1297
- return ok12({
1764
+ return ok15({
1298
1765
  name: targetFrame.name(),
1299
1766
  url: targetFrame.url()
1300
1767
  });
@@ -1302,27 +1769,27 @@ var frameCommand = registerCommand({
1302
1769
  });
1303
1770
 
1304
1771
  // src/commands/ui-debug.ts
1305
- import { z as z13 } from "zod";
1306
- import { ok as ok13 } from "@dyyz1993/xcli-core";
1772
+ import { z as z16 } from "zod";
1773
+ import { ok as ok16 } from "@dyyz1993/xcli-core";
1307
1774
  var consoleCheckCommand = registerCommand({
1308
1775
  name: "console",
1309
1776
  description: "Collect and analyze browser console messages (errors, warnings, logs)",
1310
1777
  scope: "page",
1311
- parameters: z13.object({
1312
- url: z13.string().optional().describe("URL to navigate first (optional, uses current page if omitted)"),
1313
- duration: z13.number().optional().default(5e3).describe("How long to collect messages (ms)"),
1314
- filter: z13.enum(["all", "error", "warning", "info", "log"]).optional().default("all"),
1315
- includeStackTraces: z13.boolean().optional().default(true)
1778
+ parameters: z16.object({
1779
+ url: z16.string().optional().describe("URL to navigate first (optional, uses current page if omitted)"),
1780
+ duration: z16.number().optional().default(5e3).describe("How long to collect messages (ms)"),
1781
+ filter: z16.enum(["all", "error", "warning", "info", "log"]).optional().default("all"),
1782
+ includeStackTraces: z16.boolean().optional().default(true)
1316
1783
  }),
1317
- result: z13.object({
1318
- url: z13.string(),
1319
- duration: z13.number(),
1320
- total: z13.number(),
1321
- errors: z13.number(),
1322
- warnings: z13.number(),
1323
- messages: z13.array(z13.record(z13.unknown())),
1324
- summary: z13.string(),
1325
- passed: z13.boolean()
1784
+ result: z16.object({
1785
+ url: z16.string(),
1786
+ duration: z16.number(),
1787
+ total: z16.number(),
1788
+ errors: z16.number(),
1789
+ warnings: z16.number(),
1790
+ messages: z16.array(z16.record(z16.unknown())),
1791
+ summary: z16.string(),
1792
+ passed: z16.boolean()
1326
1793
  }),
1327
1794
  handler: async (p, ctx) => {
1328
1795
  const { page } = ctx;
@@ -1404,7 +1871,7 @@ ${a.stack || ""}`;
1404
1871
  const filtered = p.filter === "all" ? messages : messages.filter((m) => m.type === p.filter);
1405
1872
  const errorCount = messages.filter((m) => m.type === "error").length;
1406
1873
  const warnCount = messages.filter((m) => m.type === "warning").length;
1407
- return ok13({
1874
+ return ok16({
1408
1875
  url: page.url(),
1409
1876
  duration: p.duration,
1410
1877
  total: messages.length,
@@ -1420,23 +1887,23 @@ var networkCheckCommand = registerCommand({
1420
1887
  name: "net-debug",
1421
1888
  description: "Monitor and analyze network requests \u2014 capture failed requests, slow responses, status codes",
1422
1889
  scope: "page",
1423
- parameters: z13.object({
1424
- url: z13.string().optional().describe("URL to navigate first"),
1425
- duration: z13.number().optional().default(5e3).describe("How long to monitor (ms)"),
1426
- filter: z13.enum(["all", "failed", "slow", "error", "xhr", "fetch", "document", "stylesheet", "script", "image"]).optional().default("all"),
1427
- slowThreshold: z13.number().optional().default(3e3).describe('Threshold for "slow" requests (ms)')
1890
+ parameters: z16.object({
1891
+ url: z16.string().optional().describe("URL to navigate first"),
1892
+ duration: z16.number().optional().default(5e3).describe("How long to monitor (ms)"),
1893
+ filter: z16.enum(["all", "failed", "slow", "error", "xhr", "fetch", "document", "stylesheet", "script", "image"]).optional().default("all"),
1894
+ slowThreshold: z16.number().optional().default(3e3).describe('Threshold for "slow" requests (ms)')
1428
1895
  }),
1429
- result: z13.object({
1430
- url: z13.string(),
1431
- duration: z13.number(),
1432
- totalRequests: z13.number(),
1433
- failedRequests: z13.number(),
1434
- slowRequests: z13.number(),
1435
- errorRequests: z13.number(),
1436
- totalSizeKB: z13.number(),
1437
- requests: z13.array(z13.record(z13.unknown())),
1438
- summary: z13.string(),
1439
- passed: z13.boolean()
1896
+ result: z16.object({
1897
+ url: z16.string(),
1898
+ duration: z16.number(),
1899
+ totalRequests: z16.number(),
1900
+ failedRequests: z16.number(),
1901
+ slowRequests: z16.number(),
1902
+ errorRequests: z16.number(),
1903
+ totalSizeKB: z16.number(),
1904
+ requests: z16.array(z16.record(z16.unknown())),
1905
+ summary: z16.string(),
1906
+ passed: z16.boolean()
1440
1907
  }),
1441
1908
  handler: async (p, ctx) => {
1442
1909
  const { page } = ctx;
@@ -1518,7 +1985,7 @@ var networkCheckCommand = registerCommand({
1518
1985
  const slowCount = requests.filter((r) => r.duration >= p.slowThreshold).length;
1519
1986
  const errorCount = requests.filter((r) => r.error).length;
1520
1987
  const totalSize = requests.reduce((sum, r) => sum + r.size, 0);
1521
- return ok13({
1988
+ return ok16({
1522
1989
  url: page.url(),
1523
1990
  duration: p.duration,
1524
1991
  totalRequests: requests.length,
@@ -1540,17 +2007,17 @@ var perfCheckCommand = registerCommand({
1540
2007
  name: "perf",
1541
2008
  description: "Audit page performance metrics \u2014 load time, FCP, LCP, CLS, TTFB, resource sizes",
1542
2009
  scope: "page",
1543
- parameters: z13.object({
1544
- url: z13.string().optional().describe("URL to navigate (uses current page if omitted)"),
1545
- iterations: z13.number().optional().default(1).describe("Number of iterations to average")
2010
+ parameters: z16.object({
2011
+ url: z16.string().optional().describe("URL to navigate (uses current page if omitted)"),
2012
+ iterations: z16.number().optional().default(1).describe("Number of iterations to average")
1546
2013
  }),
1547
- result: z13.object({
1548
- url: z13.string(),
1549
- iterations: z13.number(),
1550
- metrics: z13.record(z13.unknown()),
1551
- allIterations: z13.array(z13.record(z13.unknown())).optional(),
1552
- passed: z13.boolean(),
1553
- summary: z13.string()
2014
+ result: z16.object({
2015
+ url: z16.string(),
2016
+ iterations: z16.number(),
2017
+ metrics: z16.record(z16.unknown()),
2018
+ allIterations: z16.array(z16.record(z16.unknown())).optional(),
2019
+ passed: z16.boolean(),
2020
+ summary: z16.string()
1554
2021
  }),
1555
2022
  handler: async (p, ctx) => {
1556
2023
  const { page } = ctx;
@@ -1612,7 +2079,7 @@ var perfCheckCommand = registerCommand({
1612
2079
  if (resourceStats) avg.resourceStats = resourceStats;
1613
2080
  return avg;
1614
2081
  })();
1615
- return ok13({
2082
+ return ok16({
1616
2083
  url: page.url(),
1617
2084
  iterations: p.iterations,
1618
2085
  metrics: avgMetrics,
@@ -1626,23 +2093,23 @@ var healthCheckCommand = registerCommand({
1626
2093
  name: "health",
1627
2094
  description: "Comprehensive page health check \u2014 broken links, missing images, console errors, SEO issues",
1628
2095
  scope: "page",
1629
- parameters: z13.object({
1630
- url: z13.string().optional().describe("URL to check"),
1631
- checkLinks: z13.boolean().optional().default(true).describe("Check for broken links"),
1632
- checkImages: z13.boolean().optional().default(true).describe("Check for missing/broken images"),
1633
- checkMeta: z13.boolean().optional().default(true).describe("Check SEO meta tags"),
1634
- maxLinks: z13.number().optional().default(50).describe("Max links to check")
2096
+ parameters: z16.object({
2097
+ url: z16.string().optional().describe("URL to check"),
2098
+ checkLinks: z16.boolean().optional().default(true).describe("Check for broken links"),
2099
+ checkImages: z16.boolean().optional().default(true).describe("Check for missing/broken images"),
2100
+ checkMeta: z16.boolean().optional().default(true).describe("Check SEO meta tags"),
2101
+ maxLinks: z16.number().optional().default(50).describe("Max links to check")
1635
2102
  }),
1636
- result: z13.object({
1637
- url: z13.string(),
1638
- title: z13.string(),
1639
- passed: z13.boolean(),
1640
- totalIssues: z13.number(),
1641
- errors: z13.number(),
1642
- warnings: z13.number(),
1643
- info: z13.number(),
1644
- issues: z13.array(z13.record(z13.unknown())),
1645
- summary: z13.string()
2103
+ result: z16.object({
2104
+ url: z16.string(),
2105
+ title: z16.string(),
2106
+ passed: z16.boolean(),
2107
+ totalIssues: z16.number(),
2108
+ errors: z16.number(),
2109
+ warnings: z16.number(),
2110
+ info: z16.number(),
2111
+ issues: z16.array(z16.record(z16.unknown())),
2112
+ summary: z16.string()
1646
2113
  }),
1647
2114
  handler: async (p, ctx) => {
1648
2115
  const { page } = ctx;
@@ -1729,7 +2196,7 @@ var healthCheckCommand = registerCommand({
1729
2196
  }, { checkLinks: p.checkLinks, checkImages: p.checkImages, checkMeta: p.checkMeta, maxLinks: p.maxLinks });
1730
2197
  const errors = result.issues.filter((i) => i.severity === "error").length;
1731
2198
  const warnings = result.issues.filter((i) => i.severity === "warning").length;
1732
- return ok13({
2199
+ return ok16({
1733
2200
  url: result.url,
1734
2201
  title: result.title,
1735
2202
  passed: errors === 0,
@@ -1744,68 +2211,66 @@ var healthCheckCommand = registerCommand({
1744
2211
  });
1745
2212
 
1746
2213
  // src/commands/actions.ts
1747
- import { z as z14 } from "zod";
1748
- import { ok as ok14 } from "@dyyz1993/xcli-core";
2214
+ import { z as z17 } from "zod";
2215
+ import { ok as ok17 } from "@dyyz1993/xcli-core";
1749
2216
  import { writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
1750
- import { join as join3 } from "path";
1751
- import { homedir as homedir3 } from "os";
1752
- var SCREENSHOTS_DIR2 = join3(homedir3(), ".xbrowser", "screenshots");
2217
+ import { join as join5 } from "path";
1753
2218
  function ensureScreenshotsDir2() {
1754
- mkdirSync2(SCREENSHOTS_DIR2, { recursive: true });
2219
+ mkdirSync2(resolveScreenshotsDir(), { recursive: true });
1755
2220
  }
1756
2221
  function generateScreenshotPath2(format) {
1757
2222
  const timestamp = Date.now();
1758
2223
  const random = Math.random().toString(36).slice(2, 8);
1759
2224
  const ext = format === "jpeg" ? "jpg" : "png";
1760
- return join3(SCREENSHOTS_DIR2, `screenshot-${timestamp}-${random}.${ext}`);
2225
+ return join5(resolveScreenshotsDir(), `screenshot-${timestamp}-${random}.${ext}`);
1761
2226
  }
1762
- var waitActionSchema = z14.object({
1763
- type: z14.literal("wait"),
1764
- milliseconds: z14.number().positive().optional(),
1765
- selector: z14.string().optional()
2227
+ var waitActionSchema = z17.object({
2228
+ type: z17.literal("wait"),
2229
+ milliseconds: z17.number().positive().optional(),
2230
+ selector: z17.string().optional()
1766
2231
  }).refine(
1767
2232
  (d) => (d.milliseconds !== void 0 || d.selector !== void 0) && !(d.milliseconds !== void 0 && d.selector !== void 0),
1768
2233
  { message: "Either 'milliseconds' or 'selector' must be provided, but not both." }
1769
2234
  );
1770
- var clickActionSchema = z14.object({
1771
- type: z14.literal("click"),
1772
- selector: z14.string(),
1773
- all: z14.boolean().optional()
2235
+ var clickActionSchema = z17.object({
2236
+ type: z17.literal("click"),
2237
+ selector: z17.string(),
2238
+ all: z17.boolean().optional()
1774
2239
  });
1775
- var screenshotActionSchema = z14.object({
1776
- type: z14.literal("screenshot"),
1777
- fullPage: z14.boolean().optional(),
1778
- quality: z14.number().min(1).max(100).optional(),
1779
- viewport: z14.object({ width: z14.number().int().positive(), height: z14.number().int().positive() }).optional(),
1780
- base64: z14.boolean().optional().describe("Return base64 data instead of file path")
2240
+ var screenshotActionSchema = z17.object({
2241
+ type: z17.literal("screenshot"),
2242
+ fullPage: z17.boolean().optional(),
2243
+ quality: z17.number().min(1).max(100).optional(),
2244
+ viewport: z17.object({ width: z17.number().int().positive(), height: z17.number().int().positive() }).optional(),
2245
+ base64: z17.boolean().optional().describe("Return base64 data instead of file path")
1781
2246
  });
1782
- var writeActionSchema = z14.object({
1783
- type: z14.literal("write"),
1784
- text: z14.string()
2247
+ var writeActionSchema = z17.object({
2248
+ type: z17.literal("write"),
2249
+ text: z17.string()
1785
2250
  });
1786
- var pressActionSchema = z14.object({
1787
- type: z14.literal("press"),
1788
- key: z14.string()
2251
+ var pressActionSchema = z17.object({
2252
+ type: z17.literal("press"),
2253
+ key: z17.string()
1789
2254
  });
1790
- var scrollActionSchema = z14.object({
1791
- type: z14.literal("scroll"),
1792
- direction: z14.enum(["up", "down"]).optional(),
1793
- selector: z14.string().optional()
2255
+ var scrollActionSchema = z17.object({
2256
+ type: z17.literal("scroll"),
2257
+ direction: z17.enum(["up", "down"]).optional(),
2258
+ selector: z17.string().optional()
1794
2259
  });
1795
- var scrapeActionSchema = z14.object({
1796
- type: z14.literal("scrape")
2260
+ var scrapeActionSchema = z17.object({
2261
+ type: z17.literal("scrape")
1797
2262
  });
1798
- var executeJavascriptActionSchema = z14.object({
1799
- type: z14.literal("executeJavascript"),
1800
- script: z14.string()
2263
+ var executeJavascriptActionSchema = z17.object({
2264
+ type: z17.literal("executeJavascript"),
2265
+ script: z17.string()
1801
2266
  });
1802
- var pdfActionSchema = z14.object({
1803
- type: z14.literal("pdf"),
1804
- landscape: z14.boolean().optional(),
1805
- scale: z14.number().optional(),
1806
- format: z14.enum(["A0", "A1", "A2", "A3", "A4", "A5", "A6", "Letter", "Legal", "Tabloid", "Ledger"]).optional()
2267
+ var pdfActionSchema = z17.object({
2268
+ type: z17.literal("pdf"),
2269
+ landscape: z17.boolean().optional(),
2270
+ scale: z17.number().optional(),
2271
+ format: z17.enum(["A0", "A1", "A2", "A3", "A4", "A5", "A6", "Letter", "Legal", "Tabloid", "Ledger"]).optional()
1807
2272
  });
1808
- var actionSchema = z14.union([
2273
+ var actionSchema = z17.union([
1809
2274
  waitActionSchema,
1810
2275
  clickActionSchema,
1811
2276
  screenshotActionSchema,
@@ -1896,11 +2361,11 @@ var actionsCommand = registerCommand({
1896
2361
  name: "actions",
1897
2362
  description: "Execute a sequence of actions (wait, click, scroll, screenshot, fill, etc.)",
1898
2363
  scope: "page",
1899
- parameters: z14.object({
1900
- url: z14.string().describe("Starting URL"),
1901
- actions: z14.array(actionSchema).max(MAX_ACTIONS).describe("Array of actions (max 50)"),
1902
- output: z14.enum(["text", "json"]).default("json").describe("Output format: text or json"),
1903
- timeout: z14.number().default(60).describe("Overall timeout in seconds (default: 60)")
2364
+ parameters: z17.object({
2365
+ url: z17.string().describe("Starting URL"),
2366
+ actions: z17.array(actionSchema).max(MAX_ACTIONS).describe("Array of actions (max 50)"),
2367
+ output: z17.enum(["text", "json"]).default("json").describe("Output format: text or json"),
2368
+ timeout: z17.number().default(60).describe("Overall timeout in seconds (default: 60)")
1904
2369
  }),
1905
2370
  handler: async (p, ctx) => {
1906
2371
  await ctx.page.goto(p.url, { waitUntil: "domcontentloaded" });
@@ -1920,14 +2385,14 @@ var actionsCommand = registerCommand({
1920
2385
  const finalUrl = ctx.page.url();
1921
2386
  const timedOut = results.length < p.actions.length;
1922
2387
  if (p.output === "text") {
1923
- return ok14({
2388
+ return ok17({
1924
2389
  title,
1925
2390
  url: finalUrl,
1926
2391
  actions: results.map((r) => JSON.stringify(r)).join("\n"),
1927
2392
  ...timedOut ? { warning: `Timed out after ${p.timeout}s, completed ${results.length}/${p.actions.length} actions` } : {}
1928
2393
  });
1929
2394
  }
1930
- return ok14({
2395
+ return ok17({
1931
2396
  title,
1932
2397
  url: finalUrl,
1933
2398
  results,
@@ -1937,8 +2402,8 @@ var actionsCommand = registerCommand({
1937
2402
  });
1938
2403
 
1939
2404
  // src/commands/scrape.ts
1940
- import { z as z15 } from "zod";
1941
- import { ok as ok15, fail as fail5 } from "@dyyz1993/xcli-core";
2405
+ import { z as z18 } from "zod";
2406
+ import { ok as ok18, fail as fail7 } from "@dyyz1993/xcli-core";
1942
2407
 
1943
2408
  // src/lib/html-to-markdown.ts
1944
2409
  import * as cheerio from "cheerio";
@@ -2323,15 +2788,15 @@ var scrapeCommand = registerCommand({
2323
2788
  description: "Scrape a page and convert to Markdown (with JS rendering)",
2324
2789
  scope: "project",
2325
2790
  selectorParams: ["selector"],
2326
- parameters: z15.object({
2327
- url: z15.string().optional(),
2328
- selector: z15.string().optional(),
2329
- timeout: z15.number().default(3e4),
2330
- format: z15.enum(["markdown", "html", "text"]).default("markdown"),
2331
- onlyMainContent: z15.boolean().default(true),
2332
- retries: z15.number().int().min(0).max(5).optional().default(2).describe("\u91CD\u8BD5\u6B21\u6570\uFF08\u9ED8\u8BA4 2\uFF09"),
2333
- waitAfterLoad: z15.number().int().optional().default(0).describe("\u9875\u9762\u52A0\u8F7D\u540E\u989D\u5916\u7B49\u5F85\u6BEB\u79D2"),
2334
- 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")
2791
+ parameters: z18.object({
2792
+ url: z18.string().optional(),
2793
+ selector: z18.string().optional(),
2794
+ timeout: z18.number().default(3e4),
2795
+ format: z18.enum(["markdown", "html", "text"]).default("markdown"),
2796
+ onlyMainContent: z18.boolean().default(true),
2797
+ retries: z18.number().int().min(0).max(5).optional().default(2).describe("\u91CD\u8BD5\u6B21\u6570\uFF08\u9ED8\u8BA4 2\uFF09"),
2798
+ waitAfterLoad: z18.number().int().optional().default(0).describe("\u9875\u9762\u52A0\u8F7D\u540E\u989D\u5916\u7B49\u5F85\u6BEB\u79D2"),
2799
+ 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")
2335
2800
  }),
2336
2801
  handler: async (p, ctx) => {
2337
2802
  const { context, page } = await createEphemeralContext(resolveLaunchOpts(ctx));
@@ -2339,7 +2804,7 @@ var scrapeCommand = registerCommand({
2339
2804
  try {
2340
2805
  const targetUrl = p.url || page.url();
2341
2806
  if (!targetUrl || targetUrl === "about:blank") {
2342
- return fail5("URL is required. Provide a URL or connect to a browser (--cdp) with a non-blank page.");
2807
+ return fail7("URL is required. Provide a URL or connect to a browser (--cdp) with a non-blank page.");
2343
2808
  }
2344
2809
  let lastError;
2345
2810
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
@@ -2409,7 +2874,7 @@ var scrapeCommand = registerCommand({
2409
2874
  } catch {
2410
2875
  }
2411
2876
  if (p.mode === "smart") {
2412
- return ok15(structured);
2877
+ return ok18(structured);
2413
2878
  }
2414
2879
  if (p.mode === "compact") {
2415
2880
  const compactData = structured.tables.length > 0 ? structured.tables.map((t) => ({
@@ -2422,9 +2887,9 @@ var scrapeCommand = registerCommand({
2422
2887
  return compact;
2423
2888
  })
2424
2889
  })) : structured.mainText?.substring(0, 500);
2425
- return ok15({ url: structured.url, title: structured.title, data: compactData });
2890
+ return ok18({ url: structured.url, title: structured.title, data: compactData });
2426
2891
  }
2427
- return ok15(structured);
2892
+ return ok18(structured);
2428
2893
  }
2429
2894
  let content;
2430
2895
  switch (p.format) {
@@ -2483,7 +2948,7 @@ var scrapeCommand = registerCommand({
2483
2948
  content = await page.innerText("body");
2484
2949
  break;
2485
2950
  }
2486
- return ok15({ content, title, url: finalUrl });
2951
+ return ok18({ content, title, url: finalUrl });
2487
2952
  } catch (err) {
2488
2953
  lastError = err instanceof Error ? err : new Error(String(err));
2489
2954
  if (attempt < maxAttempts) {
@@ -2493,20 +2958,20 @@ var scrapeCommand = registerCommand({
2493
2958
  }
2494
2959
  }
2495
2960
  }
2496
- return fail5(`Scrape failed after ${maxAttempts} attempt(s): ${lastError?.message ?? "unknown error"}`);
2961
+ return fail7(`Scrape failed after ${maxAttempts} attempt(s): ${lastError?.message ?? "unknown error"}`);
2497
2962
  } finally {
2498
2963
  await closeEphemeralContext(context);
2499
2964
  }
2500
2965
  },
2501
- result: z15.object({
2502
- url: z15.string(),
2503
- title: z15.string()
2966
+ result: z18.object({
2967
+ url: z18.string(),
2968
+ title: z18.string()
2504
2969
  }).passthrough()
2505
2970
  });
2506
2971
 
2507
2972
  // src/commands/map.ts
2508
- import { z as z16 } from "zod";
2509
- import { ok as ok16, fail as fail6 } from "@dyyz1993/xcli-core";
2973
+ import { z as z19 } from "zod";
2974
+ import { ok as ok19, fail as fail8 } from "@dyyz1993/xcli-core";
2510
2975
 
2511
2976
  // src/utils/url.ts
2512
2977
  var SKIP_EXTENSIONS = /* @__PURE__ */ new Set([
@@ -2741,21 +3206,21 @@ var mapCommand = registerCommand({
2741
3206
  name: "map",
2742
3207
  description: "Discover all URLs on a website via sitemap and page link extraction",
2743
3208
  scope: "project",
2744
- parameters: z16.object({
2745
- url: z16.string().optional(),
2746
- search: z16.string().optional(),
2747
- sitemap: z16.enum(["include", "only"]).optional(),
2748
- includeSubdomains: z16.boolean().optional(),
2749
- allowExternalLinks: z16.boolean().optional().describe("Include links to external domains"),
2750
- limit: z16.number().optional(),
2751
- verbose: z16.boolean().default(false).describe("Show progress feedback")
3209
+ parameters: z19.object({
3210
+ url: z19.string().optional(),
3211
+ search: z19.string().optional(),
3212
+ sitemap: z19.enum(["include", "only"]).optional(),
3213
+ includeSubdomains: z19.boolean().optional(),
3214
+ allowExternalLinks: z19.boolean().optional().describe("Include links to external domains"),
3215
+ limit: z19.number().optional(),
3216
+ verbose: z19.boolean().default(false).describe("Show progress feedback")
2752
3217
  }),
2753
3218
  handler: async (p, ctx) => {
2754
3219
  const { context, page } = await createEphemeralContext(resolveLaunchOpts(ctx));
2755
3220
  try {
2756
3221
  const targetUrl = p.url || page.url();
2757
3222
  if (!targetUrl || targetUrl === "about:blank") {
2758
- return fail6("URL is required. Provide a URL or connect to a browser (--cdp) with a non-blank page.");
3223
+ return fail8("URL is required. Provide a URL or connect to a browser (--cdp) with a non-blank page.");
2759
3224
  }
2760
3225
  const links = await discoverUrls(page, targetUrl, {
2761
3226
  sitemap: p.sitemap,
@@ -2766,7 +3231,7 @@ var mapCommand = registerCommand({
2766
3231
  verbose: p.verbose
2767
3232
  });
2768
3233
  const linkObjects = links.map((url) => ({ url }));
2769
- return ok16({
3234
+ return ok19({
2770
3235
  links: linkObjects,
2771
3236
  success: true
2772
3237
  });
@@ -2774,15 +3239,15 @@ var mapCommand = registerCommand({
2774
3239
  await closeEphemeralContext(context);
2775
3240
  }
2776
3241
  },
2777
- result: z16.object({
2778
- links: z16.array(z16.object({ url: z16.string() })),
2779
- success: z16.boolean()
3242
+ result: z19.object({
3243
+ links: z19.array(z19.object({ url: z19.string() })),
3244
+ success: z19.boolean()
2780
3245
  })
2781
3246
  });
2782
3247
 
2783
3248
  // src/commands/crawl.ts
2784
- import { z as z17 } from "zod";
2785
- import { ok as ok17 } from "@dyyz1993/xcli-core";
3249
+ import { z as z20 } from "zod";
3250
+ import { ok as ok20 } from "@dyyz1993/xcli-core";
2786
3251
  function stripHashAnchorQuery(url) {
2787
3252
  try {
2788
3253
  const parsed = new URL(url);
@@ -2959,21 +3424,21 @@ var crawlCommand = registerCommand({
2959
3424
  name: "crawl",
2960
3425
  description: "Crawl a website and extract content from all pages",
2961
3426
  scope: "project",
2962
- parameters: z17.object({
2963
- url: z17.string(),
2964
- limit: z17.number().default(10),
2965
- maxDepth: z17.number().default(3),
2966
- includePaths: z17.array(z17.string()).optional(),
2967
- excludePaths: z17.array(z17.string()).optional(),
2968
- allowSubdomains: z17.boolean().default(false),
2969
- allowExternalLinks: z17.boolean().default(false),
2970
- allowBackwardCrawling: z17.boolean().default(false),
2971
- enableSpa: z17.boolean().default(true).describe("Disable to skip SPA route detection"),
2972
- format: z17.enum(["markdown", "html"]).default("markdown"),
2973
- onlyMainContent: z17.boolean().default(true),
2974
- concurrency: z17.number().default(3),
2975
- retries: z17.number().default(2),
2976
- verbose: z17.boolean().default(false)
3427
+ parameters: z20.object({
3428
+ url: z20.string(),
3429
+ limit: z20.number().default(10),
3430
+ maxDepth: z20.number().default(3),
3431
+ includePaths: z20.array(z20.string()).optional(),
3432
+ excludePaths: z20.array(z20.string()).optional(),
3433
+ allowSubdomains: z20.boolean().default(false),
3434
+ allowExternalLinks: z20.boolean().default(false),
3435
+ allowBackwardCrawling: z20.boolean().default(false),
3436
+ enableSpa: z20.boolean().default(true).describe("Disable to skip SPA route detection"),
3437
+ format: z20.enum(["markdown", "html"]).default("markdown"),
3438
+ onlyMainContent: z20.boolean().default(true),
3439
+ concurrency: z20.number().default(3),
3440
+ retries: z20.number().default(2),
3441
+ verbose: z20.boolean().default(false)
2977
3442
  }),
2978
3443
  handler: async (p, ctx) => {
2979
3444
  const startUrl = new URL(p.url);
@@ -3112,7 +3577,7 @@ var crawlCommand = registerCommand({
3112
3577
  if (errorPages.length > 0) {
3113
3578
  response.errors = errorPages;
3114
3579
  }
3115
- return ok17(response);
3580
+ return ok20(response);
3116
3581
  } finally {
3117
3582
  for (const ctx2 of contexts) {
3118
3583
  await ctx2.close().catch(() => {
@@ -3122,15 +3587,15 @@ var crawlCommand = registerCommand({
3122
3587
  } catch (err) {
3123
3588
  const message = err instanceof Error ? err.message : String(err);
3124
3589
  const successPages = results.filter((r) => !isPageError(r));
3125
- return ok17({ pages: successPages, total: successPages.length, success: false, error: message });
3590
+ return ok20({ pages: successPages, total: successPages.length, success: false, error: message });
3126
3591
  }
3127
3592
  }
3128
3593
  });
3129
3594
 
3130
3595
  // src/commands/search.ts
3131
- import { z as z18 } from "zod";
3596
+ import { z as z21 } from "zod";
3132
3597
  import * as cheerio2 from "cheerio";
3133
- import { ok as ok18 } from "@dyyz1993/xcli-core";
3598
+ import { ok as ok21 } from "@dyyz1993/xcli-core";
3134
3599
  function getRecencyParams(recency) {
3135
3600
  const now = Math.floor(Date.now() / 1e3);
3136
3601
  switch (recency) {
@@ -3464,16 +3929,16 @@ var searchCommand = registerCommand({
3464
3929
  name: "search",
3465
3930
  description: "Search the web and extract results with engine fallback",
3466
3931
  scope: "project",
3467
- parameters: z18.object({
3468
- query: z18.string(),
3469
- engine: z18.string().optional(),
3470
- limit: z18.number().default(10),
3471
- full: z18.boolean().default(false),
3472
- format: z18.enum(["markdown", "json", "text"]).default("markdown"),
3473
- timeout: z18.number().default(15e3),
3474
- recency: z18.enum(["hour", "day", "week", "month", "year"]).optional().describe("Filter by time: hour/day/week/month/year"),
3475
- fallback: z18.boolean().default(false).describe("Sequential engine fallback instead of parallel"),
3476
- site: z18.string().optional().describe("Limit results to a specific site (e.g. github.com, v2ex.com)")
3932
+ parameters: z21.object({
3933
+ query: z21.string(),
3934
+ engine: z21.string().optional(),
3935
+ limit: z21.number().default(10),
3936
+ full: z21.boolean().default(false),
3937
+ format: z21.enum(["markdown", "json", "text"]).default("markdown"),
3938
+ timeout: z21.number().default(15e3),
3939
+ recency: z21.enum(["hour", "day", "week", "month", "year"]).optional().describe("Filter by time: hour/day/week/month/year"),
3940
+ fallback: z21.boolean().default(false).describe("Sequential engine fallback instead of parallel"),
3941
+ site: z21.string().optional().describe("Limit results to a specific site (e.g. github.com, v2ex.com)")
3477
3942
  }),
3478
3943
  handler: async (p, ctx) => {
3479
3944
  const { context } = await createEphemeralContext(resolveLaunchOpts(ctx));
@@ -3579,7 +4044,7 @@ ${errors.map((e) => ` - ${e.engine}: ${e.error}`).join("\n")}`
3579
4044
  lines.push(`> ${r.snippet}`);
3580
4045
  lines.push("");
3581
4046
  }
3582
- return ok18({ ...searchResult, content: lines.join("\n") });
4047
+ return ok21({ ...searchResult, content: lines.join("\n") });
3583
4048
  }
3584
4049
  if (p.format === "text") {
3585
4050
  const lines = [`Search: ${searchResult.query} (Engine: ${searchResult.engine}, Total: ${searchResult.total})`, ""];
@@ -3589,9 +4054,9 @@ ${errors.map((e) => ` - ${e.engine}: ${e.error}`).join("\n")}`
3589
4054
  lines.push(` ${r.snippet}`);
3590
4055
  lines.push("");
3591
4056
  }
3592
- return ok18({ ...searchResult, content: lines.join("\n") });
4057
+ return ok21({ ...searchResult, content: lines.join("\n") });
3593
4058
  }
3594
- return ok18(searchResult);
4059
+ return ok21(searchResult);
3595
4060
  } finally {
3596
4061
  await closeEphemeralContext(context);
3597
4062
  }
@@ -3599,8 +4064,8 @@ ${errors.map((e) => ` - ${e.engine}: ${e.error}`).join("\n")}`
3599
4064
  });
3600
4065
 
3601
4066
  // src/commands/network.ts
3602
- import { z as z19 } from "zod";
3603
- import { ok as ok19, fail as fail7 } from "@dyyz1993/xcli-core";
4067
+ import { z as z22 } from "zod";
4068
+ import { ok as ok22, fail as fail9 } from "@dyyz1993/xcli-core";
3604
4069
  function extractPath2(url) {
3605
4070
  try {
3606
4071
  const u = new URL(url);
@@ -3698,19 +4163,19 @@ var networkCommand = registerCommand({
3698
4163
  name: "network",
3699
4164
  description: "Capture and filter network responses from a URL",
3700
4165
  scope: "project",
3701
- parameters: z19.object({
3702
- url: z19.string(),
3703
- filter: z19.string().optional(),
3704
- match: z19.string().optional(),
3705
- search: z19.string().optional().describe("Search within all captured response bodies"),
3706
- console: z19.boolean().default(false),
3707
- timeout: z19.number().default(3e4),
3708
- wait: z19.number().default(3e3),
3709
- limit: z19.number().default(50),
3710
- format: z19.enum(["summary", "json"]).default("summary"),
3711
- ws: z19.boolean().optional().default(false).describe("Only show WebSocket messages"),
3712
- 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"),
3713
- duration: z19.number().int().optional().default(15e3).describe("\u76D1\u542C\u6A21\u5F0F\u7B49\u5F85\u65F6\u957F\uFF08\u6BEB\u79D2\uFF0C\u9ED8\u8BA4 15000\uFF09")
4166
+ parameters: z22.object({
4167
+ url: z22.string(),
4168
+ filter: z22.string().optional(),
4169
+ match: z22.string().optional(),
4170
+ search: z22.string().optional().describe("Search within all captured response bodies"),
4171
+ console: z22.boolean().default(false),
4172
+ timeout: z22.number().default(3e4),
4173
+ wait: z22.number().default(3e3),
4174
+ limit: z22.number().default(50),
4175
+ format: z22.enum(["summary", "json"]).default("summary"),
4176
+ ws: z22.boolean().optional().default(false).describe("Only show WebSocket messages"),
4177
+ 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"),
4178
+ duration: z22.number().int().optional().default(15e3).describe("\u76D1\u542C\u6A21\u5F0F\u7B49\u5F85\u65F6\u957F\uFF08\u6BEB\u79D2\uFF0C\u9ED8\u8BA4 15000\uFF09")
3714
4179
  }),
3715
4180
  handler: async (p, ctx) => {
3716
4181
  const startTime = Date.now();
@@ -3765,7 +4230,7 @@ var networkCommand = registerCommand({
3765
4230
  };
3766
4231
  if (p.listen) {
3767
4232
  const page2 = ctx.page;
3768
- if (!page2) return fail7("No active page. Use --cdp to connect first.");
4233
+ if (!page2) return fail9("No active page. Use --cdp to connect first.");
3769
4234
  const captures = [];
3770
4235
  const consoleMessages = [];
3771
4236
  const wsCaptures = [];
@@ -3792,7 +4257,7 @@ var networkCommand = registerCommand({
3792
4257
  page2.off("response", handler);
3793
4258
  const duration = Date.now() - startTime;
3794
4259
  if (p.ws && wsCaptures.length > 0) {
3795
- return ok19({
4260
+ return ok22({
3796
4261
  url: "[listen mode]",
3797
4262
  duration,
3798
4263
  total: captures.length,
@@ -3806,9 +4271,9 @@ var networkCommand = registerCommand({
3806
4271
  });
3807
4272
  }
3808
4273
  if (p.format === "json") {
3809
- return ok19(buildJsonOutput("[listen mode]", captures, consoleMessages, captures.length, wsCaptures));
4274
+ return ok22(buildJsonOutput("[listen mode]", captures, consoleMessages, captures.length, wsCaptures));
3810
4275
  }
3811
- return ok19(buildSummaryOutput("[listen mode]", duration, captures, consoleMessages, captures.length, wsCaptures));
4276
+ return ok22(buildSummaryOutput("[listen mode]", duration, captures, consoleMessages, captures.length, wsCaptures));
3812
4277
  }
3813
4278
  const { context, page } = await createEphemeralContext(resolveLaunchOpts(ctx));
3814
4279
  try {
@@ -3863,7 +4328,7 @@ var networkCommand = registerCommand({
3863
4328
  }
3864
4329
  const duration = Date.now() - startTime;
3865
4330
  if (p.ws && wsCaptures.length > 0) {
3866
- return ok19({
4331
+ return ok22({
3867
4332
  url: p.url,
3868
4333
  duration,
3869
4334
  total: captures.length,
@@ -3877,9 +4342,9 @@ var networkCommand = registerCommand({
3877
4342
  });
3878
4343
  }
3879
4344
  if (p.format === "json") {
3880
- return ok19(buildJsonOutput(p.url, results, consoleMessages, totalCount, wsCaptures, searchResults));
4345
+ return ok22(buildJsonOutput(p.url, results, consoleMessages, totalCount, wsCaptures, searchResults));
3881
4346
  }
3882
- return ok19(buildSummaryOutput(p.url, duration, results, consoleMessages, totalCount, wsCaptures));
4347
+ return ok22(buildSummaryOutput(p.url, duration, results, consoleMessages, totalCount, wsCaptures));
3883
4348
  } finally {
3884
4349
  await closeEphemeralContext(context);
3885
4350
  }
@@ -3887,7 +4352,7 @@ var networkCommand = registerCommand({
3887
4352
  });
3888
4353
 
3889
4354
  // src/commands/ai-search-engines.ts
3890
- import { z as z20 } from "zod";
4355
+ import { z as z23 } from "zod";
3891
4356
  var ENGINE_CONFIGS = {
3892
4357
  deepseek: {
3893
4358
  key: "deepseek",
@@ -4141,11 +4606,11 @@ var ENGINE_CONFIGS = {
4141
4606
  }
4142
4607
  };
4143
4608
  var ALL_ENGINE_KEYS = Object.keys(ENGINE_CONFIGS);
4144
- var ENGINE_KEY_ENUM = z20.enum(ALL_ENGINE_KEYS);
4609
+ var ENGINE_KEY_ENUM = z23.enum(ALL_ENGINE_KEYS);
4145
4610
 
4146
4611
  // src/commands/snapshot.ts
4147
- import { z as z21 } from "zod";
4148
- import { ok as ok20, fail as fail8, normalizeTips as normalizeTips3 } from "@dyyz1993/xcli-core";
4612
+ import { z as z24 } from "zod";
4613
+ import { ok as ok23, fail as fail10, normalizeTips as normalizeTips3 } from "@dyyz1993/xcli-core";
4149
4614
 
4150
4615
  // src/runtime/ref-store.ts
4151
4616
  var sessions = /* @__PURE__ */ new Map();
@@ -4386,8 +4851,8 @@ async function resolveRefParams(page, params, selectorKeys, cache, sessionId) {
4386
4851
  }
4387
4852
 
4388
4853
  // src/utils/site-semantics.ts
4389
- import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync, readFileSync } from "fs";
4390
- import { join as join4, dirname as dirname2 } from "path";
4854
+ import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
4855
+ import { join as join6, dirname as dirname2 } from "path";
4391
4856
  import { homedir as homedir4 } from "os";
4392
4857
  import { stringify, parse } from "yaml";
4393
4858
  import { execFile } from "child_process";
@@ -4465,10 +4930,10 @@ function inferAction(role, label) {
4465
4930
  return {};
4466
4931
  }
4467
4932
  function getSemanticsDir() {
4468
- return join4(homedir4(), ".xbrowser", "site-semantics");
4933
+ return join6(homedir4(), ".xbrowser", "site-semantics");
4469
4934
  }
4470
4935
  function getSemanticsPath(domain) {
4471
- return join4(getSemanticsDir(), `${domain}.yaml`);
4936
+ return join6(getSemanticsDir(), `${domain}.yaml`);
4472
4937
  }
4473
4938
  function extractDomain2(url) {
4474
4939
  try {
@@ -4482,9 +4947,9 @@ function saveSemantics(domain, pagePath, url, elements) {
4482
4947
  const filePath = getSemanticsPath(domain);
4483
4948
  const dir = dirname2(filePath);
4484
4949
  let site;
4485
- if (existsSync(filePath)) {
4950
+ if (existsSync3(filePath)) {
4486
4951
  try {
4487
- site = parse(readFileSync(filePath, "utf-8"));
4952
+ site = parse(readFileSync3(filePath, "utf-8"));
4488
4953
  } catch {
4489
4954
  site = { site: domain, pages: {} };
4490
4955
  }
@@ -4504,16 +4969,16 @@ function saveSemantics(domain, pagePath, url, elements) {
4504
4969
  }
4505
4970
  }
4506
4971
  site.updated_at = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
4507
- if (!existsSync(dir)) {
4972
+ if (!existsSync3(dir)) {
4508
4973
  mkdirSync3(dir, { recursive: true });
4509
4974
  }
4510
4975
  writeFileSync3(filePath, stringify(site, { lineWidth: 0 }));
4511
4976
  }
4512
4977
  function loadSemantics(domain) {
4513
4978
  const filePath = getSemanticsPath(domain);
4514
- if (!existsSync(filePath)) return null;
4979
+ if (!existsSync3(filePath)) return null;
4515
4980
  try {
4516
- return parse(readFileSync(filePath, "utf-8"));
4981
+ return parse(readFileSync3(filePath, "utf-8"));
4517
4982
  } catch {
4518
4983
  return null;
4519
4984
  }
@@ -5057,24 +5522,24 @@ var snapshotCommand = registerCommand({
5057
5522
  description: "Capture a quick page state snapshot \u2014 aria tree, visible text, or DOM summary",
5058
5523
  scope: "page",
5059
5524
  selectorParams: ["selector"],
5060
- parameters: z21.object({
5061
- type: z21.enum(["aria", "text", "dom", "all"]).default("aria").describe("Snapshot type: aria (accessibility tree), text (visible text), dom (element summary), all (combined)"),
5062
- selector: z21.string().optional().describe("Scope to a specific element"),
5063
- depth: z21.number().optional().default(6).describe("Max depth for DOM/aria tree"),
5064
- interactive: z21.boolean().optional().default(false).describe("Return interactive agent refs only"),
5065
- interactiveOnly: z21.boolean().optional().default(false).describe("Alias for interactive"),
5066
- i: z21.boolean().optional().default(false).describe("Short alias for interactive"),
5067
- compact: z21.boolean().optional().default(false).describe("Include compact xbrowser style snapshot text"),
5068
- c: z21.boolean().optional().default(false).describe("Short alias for compact"),
5069
- selectors: z21.boolean().optional().default(false).describe("Include ref to CSS selector map"),
5070
- all: z21.boolean().optional().default(false).describe("Include hidden interactive targets when using interactive snapshot")
5525
+ parameters: z24.object({
5526
+ type: z24.enum(["aria", "text", "dom", "all"]).default("aria").describe("Snapshot type: aria (accessibility tree), text (visible text), dom (element summary), all (combined)"),
5527
+ selector: z24.string().optional().describe("Scope to a specific element"),
5528
+ depth: z24.number().optional().default(6).describe("Max depth for DOM/aria tree"),
5529
+ interactive: z24.boolean().optional().default(false).describe("Return interactive agent refs only"),
5530
+ interactiveOnly: z24.boolean().optional().default(false).describe("Alias for interactive"),
5531
+ i: z24.boolean().optional().default(false).describe("Short alias for interactive"),
5532
+ compact: z24.boolean().optional().default(false).describe("Include compact xbrowser style snapshot text"),
5533
+ c: z24.boolean().optional().default(false).describe("Short alias for compact"),
5534
+ selectors: z24.boolean().optional().default(false).describe("Include ref to CSS selector map"),
5535
+ all: z24.boolean().optional().default(false).describe("Include hidden interactive targets when using interactive snapshot")
5071
5536
  }),
5072
- result: z21.object({
5073
- url: z21.string(),
5074
- title: z21.string(),
5075
- aria: z21.string().optional(),
5076
- text: z21.string().optional(),
5077
- dom: z21.record(z21.unknown()).optional()
5537
+ result: z24.object({
5538
+ url: z24.string(),
5539
+ title: z24.string(),
5540
+ aria: z24.string().optional(),
5541
+ text: z24.string().optional(),
5542
+ dom: z24.record(z24.unknown()).optional()
5078
5543
  }),
5079
5544
  handler: async (p, ctx) => {
5080
5545
  const page = ctx.page;
@@ -5088,7 +5553,7 @@ var snapshotCommand = registerCommand({
5088
5553
  if (p.compact || p.c || p.interactive || p.interactiveOnly || p.i) {
5089
5554
  observation.compact = formatObservationCompact(observation, { selectors: p.selectors });
5090
5555
  }
5091
- return ok20(observation, normalizeTips3([
5556
+ return ok23(observation, normalizeTips3([
5092
5557
  `refs refreshed for ${observation.targets.length} targets; use click @e1 or fill @e2 "text"`
5093
5558
  ]));
5094
5559
  }
@@ -5096,15 +5561,15 @@ var snapshotCommand = registerCommand({
5096
5561
  const aria = await captureAriaSnapshot(page, p.selector, p.depth);
5097
5562
  const tips = await buildRefTips(page, aria);
5098
5563
  persistSemantics(url, aria);
5099
- return ok20({ url, title, aria }, normalizeTips3(tips));
5564
+ return ok23({ url, title, aria }, normalizeTips3(tips));
5100
5565
  }
5101
5566
  if (p.type === "text") {
5102
5567
  const text = await captureTextSnapshot(page, p.selector);
5103
- return ok20({ url, title, text });
5568
+ return ok23({ url, title, text });
5104
5569
  }
5105
5570
  if (p.type === "dom") {
5106
5571
  const dom = await captureDomSnapshot(page, p.selector, p.depth ?? 6);
5107
- return ok20({ url, title, dom });
5572
+ return ok23({ url, title, dom });
5108
5573
  }
5109
5574
  if (p.type === "all") {
5110
5575
  const [aria, text, dom] = await Promise.all([
@@ -5114,9 +5579,9 @@ var snapshotCommand = registerCommand({
5114
5579
  ]);
5115
5580
  const tips = await buildRefTips(page, aria);
5116
5581
  persistSemantics(url, aria);
5117
- return ok20({ url, title, aria, text, dom }, normalizeTips3(tips));
5582
+ return ok23({ url, title, aria, text, dom }, normalizeTips3(tips));
5118
5583
  }
5119
- return fail8(`Unknown snapshot type: ${p.type}`);
5584
+ return fail10(`Unknown snapshot type: ${p.type}`);
5120
5585
  }
5121
5586
  });
5122
5587
  function persistSemantics(url, aria) {
@@ -5193,22 +5658,22 @@ async function captureDomSnapshot(page, selector, maxDepth) {
5193
5658
  }
5194
5659
 
5195
5660
  // src/commands/agent.ts
5196
- import { z as z22 } from "zod";
5197
- import { ok as ok21, normalizeTips as normalizeTips4 } from "@dyyz1993/xcli-core";
5661
+ import { z as z25 } from "zod";
5662
+ import { ok as ok24, normalizeTips as normalizeTips4 } from "@dyyz1993/xcli-core";
5198
5663
  var observeCommand = registerCommand({
5199
5664
  name: "observe",
5200
5665
  description: "Observe the current page as structured agent targets with session refs",
5201
5666
  scope: "page",
5202
- parameters: z22.object({
5203
- includeHidden: z22.boolean().optional().default(false).describe("Include hidden elements in the target list"),
5204
- limit: z22.number().int().positive().max(300).optional().default(80).describe("Maximum number of targets to return"),
5205
- compact: z22.boolean().optional().default(false).describe("Include compact xbrowser style snapshot text"),
5206
- selectors: z22.boolean().optional().default(false).describe("Include ref to stable CSS selector map")
5667
+ parameters: z25.object({
5668
+ includeHidden: z25.boolean().optional().default(false).describe("Include hidden elements in the target list"),
5669
+ limit: z25.number().int().positive().max(300).optional().default(80).describe("Maximum number of targets to return"),
5670
+ compact: z25.boolean().optional().default(false).describe("Include compact xbrowser style snapshot text"),
5671
+ selectors: z25.boolean().optional().default(false).describe("Include ref to stable CSS selector map")
5207
5672
  }),
5208
- result: z22.object({
5209
- targets: z22.array(z22.record(z22.unknown())),
5210
- selectors: z22.record(z22.unknown()).optional(),
5211
- compact: z22.string().optional()
5673
+ result: z25.object({
5674
+ targets: z25.array(z25.record(z25.unknown())),
5675
+ selectors: z25.record(z25.unknown()).optional(),
5676
+ compact: z25.string().optional()
5212
5677
  }).passthrough(),
5213
5678
  handler: async (p, ctx) => {
5214
5679
  const observation = await observePage(ctx.page, ctx.sessionId, {
@@ -5217,7 +5682,7 @@ var observeCommand = registerCommand({
5217
5682
  });
5218
5683
  if (p.selectors) observation.selectors = buildSelectorMap(observation);
5219
5684
  if (p.compact) observation.compact = formatObservationCompact(observation, { selectors: p.selectors });
5220
- return ok21(observation, normalizeTips4([
5685
+ return ok24(observation, normalizeTips4([
5221
5686
  `refs refreshed for ${observation.targets.length} targets; use act --ref @e1 --action click or click @e1`
5222
5687
  ]));
5223
5688
  }
@@ -5227,14 +5692,14 @@ var actCommand = registerCommand({
5227
5692
  description: "Perform an agent action using an observe ref or explicit selector",
5228
5693
  scope: "element",
5229
5694
  selectorParams: ["selector"],
5230
- parameters: z22.object({
5231
- action: z22.enum(["click", "fill", "type", "press", "select", "check", "hover"]).default("click"),
5232
- ref: z22.string().optional().describe("Session-scoped ref returned by observe, such as e1"),
5233
- selector: z22.string().optional().describe("CSS selector fallback when no ref is available"),
5234
- value: z22.string().optional().describe("Value for fill/type/select"),
5235
- key: z22.string().optional().describe("Key for press"),
5236
- force: z22.boolean().optional().default(false).describe("Bypass actionability checks"),
5237
- timeout: z22.number().optional().default(1e4).describe("Playwright action timeout in milliseconds")
5695
+ parameters: z25.object({
5696
+ action: z25.enum(["click", "fill", "type", "press", "select", "check", "hover"]).default("click"),
5697
+ ref: z25.string().optional().describe("Session-scoped ref returned by observe, such as e1"),
5698
+ selector: z25.string().optional().describe("CSS selector fallback when no ref is available"),
5699
+ value: z25.string().optional().describe("Value for fill/type/select"),
5700
+ key: z25.string().optional().describe("Key for press"),
5701
+ force: z25.boolean().optional().default(false).describe("Bypass actionability checks"),
5702
+ timeout: z25.number().optional().default(1e4).describe("Playwright action timeout in milliseconds")
5238
5703
  }).refine((p) => !!p.ref || !!p.selector, {
5239
5704
  message: "Either ref or selector is required"
5240
5705
  }),
@@ -5248,7 +5713,7 @@ var actCommand = registerCommand({
5248
5713
  tips: normalizeTips4(result.stale ? ["run observe again to refresh refs"] : [])
5249
5714
  };
5250
5715
  }
5251
- return ok21(result, normalizeTips4(result.stale ? ["ref screen hash changed; run observe if the next action is uncertain"] : []));
5716
+ return ok24(result, normalizeTips4(result.stale ? ["ref screen hash changed; run observe if the next action is uncertain"] : []));
5252
5717
  }
5253
5718
  });
5254
5719
  var waitForCommand = registerCommand({
@@ -5256,16 +5721,16 @@ var waitForCommand = registerCommand({
5256
5721
  description: "Wait for agent predicates such as text, URL, load state, selector state, or screen hash changes",
5257
5722
  scope: "page",
5258
5723
  selectorParams: ["selector"],
5259
- parameters: z22.object({
5260
- selector: z22.string().optional().describe("CSS selector or observe ref to wait for"),
5261
- state: z22.enum(["attached", "detached", "visible", "hidden"]).optional().default("visible"),
5262
- text: z22.string().optional().describe("Visible text to wait for"),
5263
- url: z22.string().optional().describe("URL substring or glob pattern to wait for"),
5264
- load: z22.enum(["load", "domcontentloaded", "networkidle"]).optional().describe("Load state to wait for"),
5265
- fn: z22.string().optional().describe("JavaScript predicate to wait for"),
5266
- screenHashChanged: z22.string().optional().describe("Previous screenHash from observe"),
5267
- timeout: z22.number().optional().default(3e4),
5268
- pollInterval: z22.number().optional().default(200)
5724
+ parameters: z25.object({
5725
+ selector: z25.string().optional().describe("CSS selector or observe ref to wait for"),
5726
+ state: z25.enum(["attached", "detached", "visible", "hidden"]).optional().default("visible"),
5727
+ text: z25.string().optional().describe("Visible text to wait for"),
5728
+ url: z25.string().optional().describe("URL substring or glob pattern to wait for"),
5729
+ load: z25.enum(["load", "domcontentloaded", "networkidle"]).optional().describe("Load state to wait for"),
5730
+ fn: z25.string().optional().describe("JavaScript predicate to wait for"),
5731
+ screenHashChanged: z25.string().optional().describe("Previous screenHash from observe"),
5732
+ timeout: z25.number().optional().default(3e4),
5733
+ pollInterval: z25.number().optional().default(200)
5269
5734
  }).refine((p) => [p.selector, p.text, p.url, p.load, p.fn, p.screenHashChanged].filter(Boolean).length === 1, {
5270
5735
  message: "Provide exactly one wait predicate: selector, text, url, load, fn, or screenHashChanged"
5271
5736
  }),
@@ -5279,30 +5744,30 @@ var waitForCommand = registerCommand({
5279
5744
  tips: []
5280
5745
  };
5281
5746
  }
5282
- return ok21(result);
5747
+ return ok24(result);
5283
5748
  }
5284
5749
  });
5285
5750
 
5286
5751
  // src/commands/tab.ts
5287
- import { z as z23 } from "zod";
5288
- import { ok as ok22, fail as fail9 } from "@dyyz1993/xcli-core";
5289
- var TabParams = z23.object({
5290
- subcommand: z23.enum(["list", "new", "close", "switch"]),
5291
- url: z23.string().optional(),
5292
- index: z23.number().int().min(0).optional()
5752
+ import { z as z26 } from "zod";
5753
+ import { ok as ok25, fail as fail11 } from "@dyyz1993/xcli-core";
5754
+ var TabParams = z26.object({
5755
+ subcommand: z26.enum(["list", "new", "close", "switch"]),
5756
+ url: z26.string().optional(),
5757
+ index: z26.number().int().min(0).optional()
5293
5758
  });
5294
5759
  var tabCommand = registerCommand({
5295
5760
  name: "tab",
5296
5761
  description: "Manage browser tabs: list, new, close, switch",
5297
5762
  scope: "page",
5298
5763
  parameters: TabParams,
5299
- result: z23.object({
5300
- success: z23.boolean(),
5301
- data: z23.unknown()
5764
+ result: z26.object({
5765
+ success: z26.boolean(),
5766
+ data: z26.unknown()
5302
5767
  }),
5303
5768
  handler: async (p, ctx) => {
5304
5769
  if (!ctx.browserContext) {
5305
- return fail9("No browser context available. Use --cdp to connect to a browser first.");
5770
+ return fail11("No browser context available. Use --cdp to connect to a browser first.");
5306
5771
  }
5307
5772
  const pages = ctx.browserContext.pages();
5308
5773
  switch (p.subcommand) {
@@ -5315,7 +5780,7 @@ var tabCommand = registerCommand({
5315
5780
  case "switch":
5316
5781
  return handleSwitch(p, pages, ctx);
5317
5782
  default:
5318
- return fail9(`Unknown subcommand: ${p.subcommand}`);
5783
+ return fail11(`Unknown subcommand: ${p.subcommand}`);
5319
5784
  }
5320
5785
  }
5321
5786
  });
@@ -5330,7 +5795,7 @@ async function handleList(pages, ctx) {
5330
5795
  if (isActive) activeIndex = i;
5331
5796
  tabs.push({ index: i, url, title, active: isActive });
5332
5797
  }
5333
- return ok22({ tabs, total: tabs.length, activeIndex });
5798
+ return ok25({ tabs, total: tabs.length, activeIndex });
5334
5799
  }
5335
5800
  async function handleNew(p, _pages, ctx) {
5336
5801
  const newPage = await ctx.browserContext.newPage();
@@ -5358,7 +5823,7 @@ async function handleNew(p, _pages, ctx) {
5358
5823
  const title = await newPage.title().catch(() => "");
5359
5824
  const allPages = ctx.browserContext.pages();
5360
5825
  const newIndex = allPages.indexOf(newPage);
5361
- return ok22({
5826
+ return ok25({
5362
5827
  index: newIndex >= 0 ? newIndex : allPages.length - 1,
5363
5828
  url: newPage.url(),
5364
5829
  title,
@@ -5369,11 +5834,11 @@ async function handleNew(p, _pages, ctx) {
5369
5834
  async function handleClose(p, ctx) {
5370
5835
  const currentPages = ctx.browserContext.pages();
5371
5836
  if (currentPages.length <= 1) {
5372
- return fail9("Cannot close the last remaining tab");
5837
+ return fail11("Cannot close the last remaining tab");
5373
5838
  }
5374
5839
  const closeIndex = p.index ?? currentPages.findIndex((pg) => pg === ctx.page);
5375
5840
  if (closeIndex < 0 || closeIndex >= currentPages.length) {
5376
- return fail9(`Invalid tab index: ${closeIndex}. Valid range: 0-${currentPages.length - 1}`);
5841
+ return fail11(`Invalid tab index: ${closeIndex}. Valid range: 0-${currentPages.length - 1}`);
5377
5842
  }
5378
5843
  const pageToClose = currentPages[closeIndex];
5379
5844
  const isActivePage = pageToClose === ctx.page;
@@ -5388,7 +5853,7 @@ async function handleClose(p, ctx) {
5388
5853
  }
5389
5854
  ctx.page = newActivePage;
5390
5855
  }
5391
- return ok22({
5856
+ return ok25({
5392
5857
  closedIndex: closeIndex,
5393
5858
  total: remainingPages.length,
5394
5859
  activeIndex: isActivePage ? closeIndex < remainingPages.length ? closeIndex : remainingPages.length - 1 : remainingPages.findIndex((pg) => pg === ctx.page)
@@ -5396,21 +5861,32 @@ async function handleClose(p, ctx) {
5396
5861
  }
5397
5862
  async function handleSwitch(p, pages, ctx) {
5398
5863
  if (p.index === void 0) {
5399
- return fail9("Parameter --index is required for switch subcommand");
5864
+ return fail11("Parameter --index is required for switch subcommand");
5400
5865
  }
5401
5866
  if (p.index < 0 || p.index >= pages.length) {
5402
- return fail9(`Invalid tab index: ${p.index}. Valid range: 0-${pages.length - 1}`);
5867
+ return fail11(`Invalid tab index: ${p.index}. Valid range: 0-${pages.length - 1}`);
5403
5868
  }
5404
5869
  const targetPage = pages[p.index];
5405
5870
  await targetPage.bringToFront().catch(() => {
5406
5871
  });
5872
+ for (let i = 0; i < 5; i++) {
5873
+ const okProbe = await targetPage.evaluate("1").then(() => true, () => false);
5874
+ if (okProbe) break;
5875
+ await new Promise((r) => setTimeout(r, 300));
5876
+ }
5877
+ try {
5878
+ const mouse = targetPage.mouse;
5879
+ if (mouse) await mouse.move(1, 1).catch(() => {
5880
+ });
5881
+ } catch {
5882
+ }
5407
5883
  const session = ctx.sessionId ? getSessionById(ctx.sessionId) : void 0;
5408
5884
  if (session) {
5409
5885
  setActivePage(session, targetPage);
5410
5886
  }
5411
5887
  ctx.page = targetPage;
5412
5888
  const title = await targetPage.title().catch(() => "");
5413
- return ok22({
5889
+ return ok25({
5414
5890
  index: p.index,
5415
5891
  url: targetPage.url(),
5416
5892
  title,
@@ -5419,9 +5895,9 @@ async function handleSwitch(p, pages, ctx) {
5419
5895
  }
5420
5896
 
5421
5897
  // src/commands/addinitscript.ts
5422
- import { z as z24 } from "zod";
5423
- import { ok as ok23 } from "@dyyz1993/xcli-core";
5424
- import { readFileSync as readFileSync2 } from "fs";
5898
+ import { z as z27 } from "zod";
5899
+ import { ok as ok26 } from "@dyyz1993/xcli-core";
5900
+ import { readFileSync as readFileSync4 } from "fs";
5425
5901
 
5426
5902
  // src/chain-parser.ts
5427
5903
  import { splitCommand, parseCommandArgs } from "@dyyz1993/xcli-core";
@@ -5561,14 +6037,14 @@ registerCommandDefinition("tab", ["subcommand"]);
5561
6037
  registerCommandDefinition("mouse", ["action", "x", "y"]);
5562
6038
 
5563
6039
  // src/commands/addinitscript.ts
5564
- var InitScriptParams = z24.object({
5565
- script: z24.string().optional(),
5566
- file: z24.string().optional(),
5567
- stdin: z24.boolean().optional(),
5568
- name: z24.string().optional(),
5569
- list: z24.boolean().optional(),
5570
- remove: z24.union([z24.string(), z24.boolean()]).optional(),
5571
- base64: z24.string().optional()
6040
+ var InitScriptParams = z27.object({
6041
+ script: z27.string().optional(),
6042
+ file: z27.string().optional(),
6043
+ stdin: z27.boolean().optional(),
6044
+ name: z27.string().optional(),
6045
+ list: z27.boolean().optional(),
6046
+ remove: z27.union([z27.string(), z27.boolean()]).optional(),
6047
+ base64: z27.string().optional()
5572
6048
  });
5573
6049
  var registeredScripts = /* @__PURE__ */ new Map();
5574
6050
  function resolveScriptContent(params) {
@@ -5576,7 +6052,7 @@ function resolveScriptContent(params) {
5576
6052
  return Buffer.from(params.base64, "base64").toString("utf-8");
5577
6053
  }
5578
6054
  if (params.file) {
5579
- return readFileSync2(params.file, "utf-8");
6055
+ return readFileSync4(params.file, "utf-8");
5580
6056
  }
5581
6057
  if (params.stdin) {
5582
6058
  return null;
@@ -5602,14 +6078,14 @@ var addInitScriptCommand = registerCommand({
5602
6078
  description: "Add an initialization script that runs on every page load",
5603
6079
  scope: "page",
5604
6080
  parameters: InitScriptParams,
5605
- result: z24.object({
5606
- scripts: z24.array(z24.object({ name: z24.string(), size: z24.number(), preview: z24.string() })).optional(),
5607
- removed: z24.string().optional(),
5608
- existed: z24.boolean().optional(),
5609
- error: z24.string().optional(),
5610
- registered: z24.string().optional(),
5611
- hint: z24.string().optional(),
5612
- executedImmediately: z24.boolean().optional()
6081
+ result: z27.object({
6082
+ scripts: z27.array(z27.object({ name: z27.string(), size: z27.number(), preview: z27.string() })).optional(),
6083
+ removed: z27.string().optional(),
6084
+ existed: z27.boolean().optional(),
6085
+ error: z27.string().optional(),
6086
+ registered: z27.string().optional(),
6087
+ hint: z27.string().optional(),
6088
+ executedImmediately: z27.boolean().optional()
5613
6089
  }).passthrough(),
5614
6090
  handler: async (params, ctx) => {
5615
6091
  if (params.list) {
@@ -5618,20 +6094,20 @@ var addInitScriptCommand = registerCommand({
5618
6094
  size: content2.length,
5619
6095
  preview: content2.slice(0, 80)
5620
6096
  }));
5621
- return ok23({ scripts });
6097
+ return ok26({ scripts });
5622
6098
  }
5623
6099
  if (params.remove && typeof params.remove === "string") {
5624
6100
  const existed = registeredScripts.delete(params.remove);
5625
- return ok23({ removed: params.remove, existed });
6101
+ return ok26({ removed: params.remove, existed });
5626
6102
  }
5627
6103
  const removeTarget = (typeof params.remove === "string" ? params.remove : null) || (params.name && !resolveScriptContent(params) ? params.name : null);
5628
6104
  if (removeTarget && !resolveScriptContent(params)) {
5629
6105
  const existed = registeredScripts.delete(removeTarget);
5630
- return ok23({ removed: removeTarget, existed });
6106
+ return ok26({ removed: removeTarget, existed });
5631
6107
  }
5632
6108
  let content = params.stdin ? await readStdin() : resolveScriptContent(params);
5633
6109
  if (!content) {
5634
- return ok23({ error: "No script content provided. Use --script, --file, --stdin, or --base64" });
6110
+ return ok26({ error: "No script content provided. Use --script, --file, --stdin, or --base64" });
5635
6111
  }
5636
6112
  const scriptName = params.name ?? `script-${Date.now()}`;
5637
6113
  registeredScripts.set(scriptName, content);
@@ -5639,45 +6115,45 @@ var addInitScriptCommand = registerCommand({
5639
6115
  try {
5640
6116
  await ctx.page.evaluate(content);
5641
6117
  } catch {
5642
- return ok23({
6118
+ return ok26({
5643
6119
  registered: scriptName,
5644
6120
  hint: "Script registered for future page loads; immediate execution skipped (page may not be ready)"
5645
6121
  });
5646
6122
  }
5647
- return ok23({ registered: scriptName, executedImmediately: true });
6123
+ return ok26({ registered: scriptName, executedImmediately: true });
5648
6124
  }
5649
6125
  });
5650
6126
  registerCommandDefinition("addinitscript", ["script"]);
5651
6127
 
5652
6128
  // src/commands/find.ts
5653
- import { z as z25 } from "zod";
5654
- import { ok as ok24, fail as fail10, normalizeTips as normalizeTips5 } from "@dyyz1993/xcli-core";
5655
- var actionSchema2 = z25.enum(["click", "fill", "type", "select", "hover", "check"]);
6129
+ import { z as z28 } from "zod";
6130
+ import { ok as ok27, fail as fail12, normalizeTips as normalizeTips5 } from "@dyyz1993/xcli-core";
6131
+ var actionSchema2 = z28.enum(["click", "fill", "type", "select", "hover", "check"]);
5656
6132
  var findCommand = registerCommand({
5657
6133
  name: "find",
5658
6134
  description: "Find elements by semantic strategy (text/role/label/placeholder/testid) and optionally perform an action",
5659
6135
  scope: "page",
5660
- parameters: z25.object({
5661
- strategy: z25.enum(["text", "role", "label", "placeholder", "testid", "alt", "title", "first", "last", "nth"]),
5662
- value: z25.string(),
5663
- name: z25.string().optional(),
5664
- exact: z25.boolean().optional().default(false),
5665
- operation: z25.string().optional().describe('Trailing operation syntax, e.g. click, fill "text", type "text"'),
6136
+ parameters: z28.object({
6137
+ strategy: z28.enum(["text", "role", "label", "placeholder", "testid", "alt", "title", "first", "last", "nth"]),
6138
+ value: z28.string(),
6139
+ name: z28.string().optional(),
6140
+ exact: z28.boolean().optional().default(false),
6141
+ operation: z28.string().optional().describe('Trailing operation syntax, e.g. click, fill "text", type "text"'),
5666
6142
  action: actionSchema2.optional().describe("Action to perform when not using trailing operation syntax"),
5667
- actionValue: z25.string().optional().describe("Value for fill/type/select when using action"),
5668
- index: z25.number().int().optional().describe("Index for nth strategy"),
5669
- click: z25.boolean().optional().default(false),
5670
- fill: z25.string().optional(),
5671
- type: z25.string().optional(),
5672
- select: z25.string().optional(),
5673
- hover: z25.boolean().optional().default(false),
5674
- check: z25.boolean().optional().default(false),
5675
- timeout: z25.number().optional().default(1e4)
6143
+ actionValue: z28.string().optional().describe("Value for fill/type/select when using action"),
6144
+ index: z28.number().int().optional().describe("Index for nth strategy"),
6145
+ click: z28.boolean().optional().default(false),
6146
+ fill: z28.string().optional(),
6147
+ type: z28.string().optional(),
6148
+ select: z28.string().optional(),
6149
+ hover: z28.boolean().optional().default(false),
6150
+ check: z28.boolean().optional().default(false),
6151
+ timeout: z28.number().optional().default(1e4)
5676
6152
  }),
5677
- result: z25.object({
5678
- matched: z25.number(),
5679
- selector: z25.string(),
5680
- action: z25.string().optional()
6153
+ result: z28.object({
6154
+ matched: z28.number(),
6155
+ selector: z28.string(),
6156
+ action: z28.string().optional()
5681
6157
  }),
5682
6158
  handler: async (p, ctx) => {
5683
6159
  const page = ctx.page;
@@ -5692,7 +6168,7 @@ var findCommand = registerCommand({
5692
6168
  });
5693
6169
  const count = await locator.count();
5694
6170
  if (count === 0) {
5695
- return fail10(`No element found with ${p.strategy}="${p.value}"`);
6171
+ return fail12(`No element found with ${p.strategy}="${p.value}"`);
5696
6172
  }
5697
6173
  const tips = [];
5698
6174
  const target = selectTarget(locator, p.strategy);
@@ -5704,15 +6180,15 @@ var findCommand = registerCommand({
5704
6180
  await target.click({ timeout: p.timeout, force: true });
5705
6181
  return okWithTips({ matched: count, selector, action: "click" }, tips);
5706
6182
  } else if (actionName === "fill") {
5707
- if (actionValue === void 0) return fail10("find fill requires a value");
6183
+ if (actionValue === void 0) return fail12("find fill requires a value");
5708
6184
  await target.fill(actionValue, { timeout: p.timeout, force: true });
5709
6185
  return okWithTips({ matched: count, selector, action: `fill("${actionValue}")` }, tips);
5710
6186
  } else if (actionName === "type") {
5711
- if (actionValue === void 0) return fail10("find type requires a value");
6187
+ if (actionValue === void 0) return fail12("find type requires a value");
5712
6188
  await target.type(actionValue, { delay: 10, timeout: p.timeout });
5713
6189
  return okWithTips({ matched: count, selector, action: `type("${actionValue}")` }, tips);
5714
6190
  } else if (actionName === "select") {
5715
- if (actionValue === void 0) return fail10("find select requires a value");
6191
+ if (actionValue === void 0) return fail12("find select requires a value");
5716
6192
  await target.selectOption(actionValue);
5717
6193
  return okWithTips({ matched: count, selector, action: `select("${actionValue}")` }, tips);
5718
6194
  } else if (actionName === "hover") {
@@ -5726,7 +6202,7 @@ var findCommand = registerCommand({
5726
6202
  }
5727
6203
  });
5728
6204
  function okWithTips(data, tips) {
5729
- const result = ok24(data);
6205
+ const result = ok27(data);
5730
6206
  if (tips.length > 0) result.tips = normalizeTips5(tips);
5731
6207
  return result;
5732
6208
  }
@@ -5827,7 +6303,7 @@ function describeSelector(strategy, value, name) {
5827
6303
  // src/context.ts
5828
6304
  function attachDetectAntiBot(ctx) {
5829
6305
  ctx.detectAntiBot = async (page, config) => {
5830
- const { detectAntiBot: detectAntiBot2 } = await import("./anti-bot-DR56Y63V.js");
6306
+ const { detectAntiBot: detectAntiBot2 } = await import("./anti-bot-GTTYNEFB.js");
5831
6307
  return detectAntiBot2(page, config);
5832
6308
  };
5833
6309
  }
@@ -6437,7 +6913,7 @@ async function loadHooks() {
6437
6913
 
6438
6914
  // src/executor.ts
6439
6915
  import { homedir as homedir5 } from "os";
6440
- import { join as join5 } from "path";
6916
+ import { join as join7 } from "path";
6441
6917
  function levenshtein(a, b) {
6442
6918
  const m = a.length, n = b.length;
6443
6919
  const dp = Array(m + 1).fill(null).map(() => Array(n + 1).fill(0));
@@ -6452,7 +6928,7 @@ function levenshtein(a, b) {
6452
6928
  }
6453
6929
  var NAVIGATION_COMMANDS = /* @__PURE__ */ new Set(["goto", "back", "forward", "refresh"]);
6454
6930
  var snapshotHintShown = /* @__PURE__ */ new WeakSet();
6455
- var CONFIG_DIR2 = join5(homedir5(), ".xbrowser");
6931
+ var CONFIG_DIR2 = join7(homedir5(), ".xbrowser");
6456
6932
  var storageCache = /* @__PURE__ */ new Map();
6457
6933
  function getPluginStorage(pluginName) {
6458
6934
  if (!storageCache.has(pluginName)) {
@@ -6464,7 +6940,7 @@ var archiveInitialized = false;
6464
6940
  function ensureArchiveInit() {
6465
6941
  if (!archiveInitialized) {
6466
6942
  try {
6467
- configureArchiveStore({ archiveDir: join5(homedir5(), ".xbrowser", "archives") });
6943
+ configureArchiveStore({ archiveDir: join7(homedir5(), ".xbrowser", "archives") });
6468
6944
  } catch {
6469
6945
  }
6470
6946
  archiveInitialized = true;
@@ -6488,7 +6964,7 @@ async function guardCheck(commandName) {
6488
6964
  }
6489
6965
  }
6490
6966
  function errorResult(message) {
6491
- return { ...fail11(message), duration: 0 };
6967
+ return { ...fail13(message), duration: 0 };
6492
6968
  }
6493
6969
  function tipsToMessages(tips) {
6494
6970
  if (!tips || tips.length === 0) return [];
@@ -6565,7 +7041,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
6565
7041
  }
6566
7042
  let targetPageOverride = null;
6567
7043
  if (_target && extraOpts?.cdpEndpoint) {
6568
- const { findTargetPage } = await import("./browser-WQZ3D6AE.js");
7044
+ const { findTargetPage } = await import("./browser-XPDMY2NX.js");
6569
7045
  targetPageOverride = await findTargetPage(extraOpts.cdpEndpoint, _target);
6570
7046
  if (!targetPageOverride) {
6571
7047
  return errorResult(`Target "${_target}" not found. Use 'xbrowser targets --cdp ${extraOpts.cdpEndpoint}' to list available pages.`);
@@ -6582,8 +7058,15 @@ async function executeCommand(commandName, params, sessionName = "default", extr
6582
7058
  params = result.data;
6583
7059
  }
6584
7060
  if (command.scope !== "cli" && !process.env.XBROWSER_DAEMON_WORKER) {
6585
- const { forwardExec } = await import("./daemon-client-GKEPT4NY.js");
6586
- const result = await forwardExec(commandName, params, sessionName, extraOpts?.cdpEndpoint);
7061
+ const { forwardExec } = await import("./daemon-client-7D6EZNOE.js");
7062
+ const longRunning = /^(vision-task|crawl|scrape|record|replay)$/.test(commandName);
7063
+ const result = await forwardExec(
7064
+ commandName,
7065
+ params,
7066
+ sessionName,
7067
+ extraOpts?.cdpEndpoint,
7068
+ longRunning ? 20 * 6e4 : void 0
7069
+ );
6587
7070
  if (result) return result;
6588
7071
  }
6589
7072
  let session;
@@ -6751,7 +7234,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
6751
7234
  timestamp: start
6752
7235
  });
6753
7236
  if (isSuccess) {
6754
- return { ...ok25(raw.data, merged.length > 0 ? merged : raw.tips), duration, ...hookOutputs ? { hookOutputs } : {} };
7237
+ return { ...ok28(raw.data, merged.length > 0 ? merged : raw.tips), duration, ...hookOutputs ? { hookOutputs } : {} };
6755
7238
  }
6756
7239
  return { success: false, data: raw.data, message: raw.message, tips: mergedOrRaw, duration, ...hookOutputs ? { hookOutputs } : {} };
6757
7240
  }
@@ -6765,14 +7248,14 @@ async function executeCommand(commandName, params, sessionName = "default", extr
6765
7248
  duration,
6766
7249
  timestamp: start
6767
7250
  });
6768
- return { ...ok25(raw, smartTipNormalized), duration, ...hookOutputs ? { hookOutputs } : {} };
7251
+ return { ...ok28(raw, smartTipNormalized), duration, ...hookOutputs ? { hookOutputs } : {} };
6769
7252
  } catch (err) {
6770
7253
  const end = Date.now();
6771
7254
  const duration = end - start;
6772
7255
  const errorMessage = errMsg(err);
6773
7256
  if (session?.page && process.env.XBROWSER_RECOVERY && !extraOpts?._recoveryAttempted) {
6774
7257
  try {
6775
- const { attemptRecovery } = await import("./recovery-EF33LKRJ.js");
7258
+ const { attemptRecovery } = await import("./recovery-Z3RDZENA.js");
6776
7259
  const recovery = await attemptRecovery(
6777
7260
  session.page,
6778
7261
  sessionName,
@@ -6810,7 +7293,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
6810
7293
  duration,
6811
7294
  timestamp: start
6812
7295
  });
6813
- return { ...fail11(errorMessage), duration };
7296
+ return { ...fail13(errorMessage), duration };
6814
7297
  } finally {
6815
7298
  }
6816
7299
  }
@@ -6851,7 +7334,7 @@ async function executeChain(input, options) {
6851
7334
  results.push({
6852
7335
  command: cmdName,
6853
7336
  raw: cmdStr,
6854
- ...fail11(`Plugin "${cmdName}" requires a sub-command`),
7337
+ ...fail13(`Plugin "${cmdName}" requires a sub-command`),
6855
7338
  duration: 0
6856
7339
  });
6857
7340
  if (type === "and") {
@@ -6870,7 +7353,7 @@ async function executeChain(input, options) {
6870
7353
  results.push({
6871
7354
  command: cmdName,
6872
7355
  raw: cmdStr,
6873
- ...fail11(`Unknown command "${subCommand}" for plugin "${cmdName}"`),
7356
+ ...fail13(`Unknown command "${subCommand}" for plugin "${cmdName}"`),
6874
7357
  duration: 0
6875
7358
  });
6876
7359
  if (type === "and") {
@@ -6974,7 +7457,7 @@ async function executeChain(input, options) {
6974
7457
  results.push({
6975
7458
  command: `${cmdName} ${subCommand}`,
6976
7459
  raw: cmdStr,
6977
- ...ok25(data),
7460
+ ...ok28(data),
6978
7461
  duration: duration2,
6979
7462
  ...hookOutputs ? { hookOutputs } : {}
6980
7463
  });
@@ -7002,7 +7485,7 @@ async function executeChain(input, options) {
7002
7485
  results.push({
7003
7486
  command: `${cmdName} ${subCommand}`,
7004
7487
  raw: cmdStr,
7005
- ...fail11(errorMessage),
7488
+ ...fail13(errorMessage),
7006
7489
  duration: duration2
7007
7490
  });
7008
7491
  if (type === "and") {
@@ -7306,10 +7789,10 @@ async function replayEntry(entry, options = {}) {
7306
7789
  }
7307
7790
 
7308
7791
  // src/daemon/feedback-store.ts
7309
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4 } from "fs";
7310
- import { join as join6 } from "path";
7792
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4 } from "fs";
7793
+ import { join as join8 } from "path";
7311
7794
  import { homedir as homedir6 } from "os";
7312
- var FEEDBACK_FILE = join6(homedir6(), ".xbrowser", "feedback.json");
7795
+ var FEEDBACK_FILE = join8(homedir6(), ".xbrowser", "feedback.json");
7313
7796
  var FeedbackStore = class {
7314
7797
  entries = [];
7315
7798
  constructor() {
@@ -7317,7 +7800,7 @@ var FeedbackStore = class {
7317
7800
  }
7318
7801
  load() {
7319
7802
  try {
7320
- const data = readFileSync3(FEEDBACK_FILE, "utf8");
7803
+ const data = readFileSync5(FEEDBACK_FILE, "utf8");
7321
7804
  this.entries = JSON.parse(data);
7322
7805
  } catch {
7323
7806
  this.entries = [];
@@ -7325,7 +7808,7 @@ var FeedbackStore = class {
7325
7808
  }
7326
7809
  save() {
7327
7810
  try {
7328
- mkdirSync4(join6(homedir6(), ".xbrowser"), { recursive: true });
7811
+ mkdirSync4(join8(homedir6(), ".xbrowser"), { recursive: true });
7329
7812
  writeFileSync4(FEEDBACK_FILE, JSON.stringify(this.entries, null, 2));
7330
7813
  } catch {
7331
7814
  }
@@ -7809,7 +8292,7 @@ function createRPCHandler() {
7809
8292
  return result;
7810
8293
  } catch (err) {
7811
8294
  const errorMessage = errMsg(err);
7812
- const { attemptRecovery } = await import("./recovery-EF33LKRJ.js");
8295
+ const { attemptRecovery } = await import("./recovery-Z3RDZENA.js");
7813
8296
  const recovery = await attemptRecovery(
7814
8297
  session?.page,
7815
8298
  sessionName,
@@ -7941,15 +8424,26 @@ function createRPCHandler() {
7941
8424
  const needsPause = INTERACTION_COMMANDS2.has(command) && !!previewWS;
7942
8425
  if (needsPause) await previewWS.pauseScreencast(sessionName);
7943
8426
  let urlBeforeCommand;
8427
+ let preElement;
7944
8428
  try {
7945
8429
  const session = findSession(sessionName);
7946
8430
  urlBeforeCommand = session?.page?.url();
8431
+ const sel = cmdParams.selector || cmdParams.css;
8432
+ if (session?.page && sel && INTERACTION_COMMANDS2.has(command)) {
8433
+ preElement = await session.page.evaluate(`
8434
+ (function() {
8435
+ const el = ${queryJS(sel)};
8436
+ if (!el || typeof window.__xb_describe !== 'function') return null;
8437
+ return window.__xb_describe(el);
8438
+ })()
8439
+ `);
8440
+ }
7947
8441
  } catch {
7948
8442
  }
7949
8443
  try {
7950
8444
  const result = await executeCommand(command, cmdParams, sessionName, { cdpEndpoint: endpoint });
7951
8445
  registerSessionIfNew(sessionName);
7952
- await injectCommandToRecorder(sessionName, command, cmdParams, urlBeforeCommand);
8446
+ await injectCommandToRecorder(sessionName, command, cmdParams, urlBeforeCommand, preElement);
7953
8447
  return result;
7954
8448
  } finally {
7955
8449
  if (needsPause) await previewWS.resumeScreencast(sessionName).catch(() => {
@@ -8104,7 +8598,7 @@ function createRPCHandler() {
8104
8598
  const limit = params.limit || 50;
8105
8599
  return { session: sessionName, commands: commandLogStore.list(sessionName, { limit }) };
8106
8600
  }
8107
- async function injectCommandToRecorder(sessionName, command, params, urlBeforeCommand) {
8601
+ async function injectCommandToRecorder(sessionName, command, params, urlBeforeCommand, preElement) {
8108
8602
  const recorder = activeRecorders.get(sessionName);
8109
8603
  if (!recorder) return;
8110
8604
  const COMMAND_ACTION_MAP = {
@@ -8112,33 +8606,48 @@ function createRPCHandler() {
8112
8606
  fill: "cdp-fill",
8113
8607
  click: "cdp-click",
8114
8608
  type: "input",
8115
- select: "change"
8609
+ select: "change",
8610
+ scroll: "scroll"
8116
8611
  };
8117
8612
  const actionType = COMMAND_ACTION_MAP[command];
8118
8613
  if (!actionType) return;
8119
8614
  const selector = params.selector || params.css;
8120
- const value = params.value || params.expression;
8615
+ let value = params.value || params.expression;
8616
+ if (value === void 0 || value === "") {
8617
+ value = params.text;
8618
+ }
8619
+ if (command === "scroll") {
8620
+ const dir = params.direction || "down";
8621
+ const dist = params.distance ?? 300;
8622
+ value = `${dir}:${dist}`;
8623
+ }
8121
8624
  let currentUrl = params.url || urlBeforeCommand;
8122
8625
  if (command !== "goto" && urlBeforeCommand && urlBeforeCommand !== "about:blank") {
8123
8626
  currentUrl = urlBeforeCommand;
8124
8627
  }
8125
- let element;
8126
- if (selector) {
8628
+ let element = preElement;
8629
+ if (!element && selector) {
8127
8630
  try {
8128
8631
  const session = findSession(sessionName);
8129
8632
  if (session?.page) {
8130
- element = await session.page.evaluate((sel) => {
8131
- const el = document.querySelector(sel);
8132
- if (!el || typeof window.__xb_describe !== "function") return null;
8133
- return window.__xb_describe(el);
8134
- }, selector);
8633
+ element = await session.page.evaluate(`
8634
+ (function() {
8635
+ const el = ${queryJS(selector)};
8636
+ if (!el || typeof window.__xb_describe !== 'function') return null;
8637
+ return window.__xb_describe(el);
8638
+ })()
8639
+ `);
8135
8640
  }
8136
8641
  } catch {
8137
8642
  }
8138
8643
  }
8139
8644
  await recorder.recordCommandAction({
8140
8645
  type: actionType,
8141
- selector,
8646
+ // Prefer the described unique selector so the dedup key matches the
8647
+ // real action signal's key (which uses element.selector). The raw
8648
+ // command selector (e.g. "button") never equals the generated unique
8649
+ // selector, so dedup silently failed before (rec-duel d02/d03).
8650
+ selector: element?.selector || selector,
8142
8651
  value,
8143
8652
  url: currentUrl,
8144
8653
  element
@@ -8323,7 +8832,7 @@ function createRPCHandler() {
8323
8832
  let rawContent;
8324
8833
  let parsed;
8325
8834
  try {
8326
- rawContent = readFileSync5(file, "utf8");
8835
+ rawContent = readFileSync7(file, "utf8");
8327
8836
  try {
8328
8837
  parsed = JSON.parse(rawContent);
8329
8838
  } catch {
@@ -8348,7 +8857,7 @@ function createRPCHandler() {
8348
8857
  if (isNewFormat) {
8349
8858
  try {
8350
8859
  const replayErrors = [];
8351
- const { SessionReplayer } = await import("./session-replayer-LJUC4TI7.js");
8860
+ const { SessionReplayer } = await import("./session-replayer-K4A6OI4M.js");
8352
8861
  const replayer = new SessionReplayer({
8353
8862
  page: session.page,
8354
8863
  stepDelay: slowMo * 500,
@@ -8398,10 +8907,10 @@ function createRPCHandler() {
8398
8907
  const errors = Array.isArray(input.errors) ? input.errors : [];
8399
8908
  const eventsPlayed = typeof input.eventsPlayed === "number" ? input.eventsPlayed : 0;
8400
8909
  const totalEvents = typeof input.totalEvents === "number" ? input.totalEvents : eventsPlayed + errors.length;
8401
- const ok26 = input.ok ?? errors.length === 0;
8910
+ const ok29 = input.ok ?? errors.length === 0;
8402
8911
  return {
8403
- ok: ok26,
8404
- success: ok26,
8912
+ ok: ok29,
8913
+ success: ok29,
8405
8914
  duration: typeof input.duration === "number" ? input.duration : 0,
8406
8915
  eventsPlayed,
8407
8916
  totalEvents,
@@ -9400,12 +9909,12 @@ var FileListHandler = class {
9400
9909
  const msg = ctx.message;
9401
9910
  try {
9402
9911
  const { readdirSync, statSync } = await import("fs");
9403
- const { join: join8, resolve } = await import("path");
9912
+ const { join: join10, resolve } = await import("path");
9404
9913
  const targetPath = resolve(msg.path);
9405
9914
  const entries = readdirSync(targetPath);
9406
9915
  const files = entries.map((name) => {
9407
9916
  try {
9408
- const stat = statSync(join8(targetPath, name));
9917
+ const stat = statSync(join10(targetPath, name));
9409
9918
  return { name, isDir: stat.isDirectory(), size: stat.size, modified: stat.mtime.toISOString() };
9410
9919
  } catch {
9411
9920
  return { name, isDir: false, size: 0, modified: "" };
@@ -9422,10 +9931,10 @@ var FileDownloadHandler = class {
9422
9931
  async handle(ctx) {
9423
9932
  const msg = ctx.message;
9424
9933
  try {
9425
- const { readFileSync: readFileSync6 } = await import("fs");
9934
+ const { readFileSync: readFileSync8 } = await import("fs");
9426
9935
  const { resolve, basename } = await import("path");
9427
9936
  const targetPath = resolve(msg.path);
9428
- const data = readFileSync6(targetPath);
9937
+ const data = readFileSync8(targetPath);
9429
9938
  const base64 = data.toString("base64");
9430
9939
  const ext = targetPath.split(".").pop()?.toLowerCase() || "";
9431
9940
  const mimeMap = {
@@ -11415,8 +11924,8 @@ connectWS();
11415
11924
  }
11416
11925
 
11417
11926
  // src/daemon/daemon-main.ts
11418
- var CONFIG_DIR3 = join7(homedir7(), ".xbrowser");
11419
- var LOG_FILE = join7(CONFIG_DIR3, "daemon.log");
11927
+ var CONFIG_DIR3 = join9(homedir7(), ".xbrowser");
11928
+ var LOG_FILE = join9(CONFIG_DIR3, "daemon.log");
11420
11929
  function log(msg) {
11421
11930
  const ts = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").substring(0, 19);
11422
11931
  const line = `[DAEMON ${ts}] ${msg}
@@ -11448,7 +11957,7 @@ async function main() {
11448
11957
  if (err.code === "EADDRINUSE") {
11449
11958
  log(`Port ${daemonPort} already in use \u2014 another daemon instance likely won the startup race. Exiting gracefully.`);
11450
11959
  try {
11451
- unlinkSync(join7(CONFIG_DIR3, "daemon.json"));
11960
+ unlinkSync(join9(CONFIG_DIR3, "daemon.json"));
11452
11961
  } catch {
11453
11962
  }
11454
11963
  process.exit(0);
@@ -11490,7 +11999,7 @@ async function main() {
11490
11999
  });
11491
12000
  });
11492
12001
  mkdirSync5(CONFIG_DIR3, { recursive: true });
11493
- writeFileSync5(join7(CONFIG_DIR3, "daemon.json"), JSON.stringify({
12002
+ writeFileSync5(join9(CONFIG_DIR3, "daemon.json"), JSON.stringify({
11494
12003
  port: daemonPort,
11495
12004
  pid: process.pid,
11496
12005
  startedAt: Date.now()