@xbrowser/cli 1.8.7 → 1.9.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.
@@ -20,7 +20,7 @@ import {
20
20
  saveSessionDiskMeta,
21
21
  setActivePage,
22
22
  touchSession
23
- } from "./chunk-SJLRCE6N.js";
23
+ } from "./chunk-Z7WC4P2M.js";
24
24
  import "./chunk-TNEN6VQ2.js";
25
25
  import "./chunk-GDKLH7ZY.js";
26
26
  import "./chunk-KFQGP6VL.js";
@@ -20,7 +20,7 @@ import {
20
20
  saveSessionDiskMeta,
21
21
  setActivePage,
22
22
  touchSession
23
- } from "./chunk-HXLMMSU3.js";
23
+ } from "./chunk-OOJ2H7IL.js";
24
24
  import "./chunk-VJNMAWPZ.js";
25
25
  import "./chunk-TNEN6VQ2.js";
26
26
  import "./chunk-GDKLH7ZY.js";
@@ -20,7 +20,7 @@ import {
20
20
  saveSessionDiskMeta,
21
21
  setActivePage,
22
22
  touchSession
23
- } from "./chunk-CJJ564VK.js";
23
+ } from "./chunk-MSQIPXKE.js";
24
24
  import "./chunk-VJNMAWPZ.js";
25
25
  import "./chunk-TNEN6VQ2.js";
26
26
  import "./chunk-GDKLH7ZY.js";
@@ -656,7 +656,7 @@ async function closeSessionByName(name) {
656
656
  } catch {
657
657
  }
658
658
  try {
659
- const { SessionRecorder } = await import("./session-recorder-33UKWCIR.js");
659
+ const { SessionRecorder } = await import("./session-recorder-KPU4YQAE.js");
660
660
  SessionRecorder.cleanup(session.name);
661
661
  } catch {
662
662
  }
@@ -2021,7 +2021,7 @@ async function closeSessionByName(name) {
2021
2021
  } catch {
2022
2022
  }
2023
2023
  try {
2024
- const { SessionRecorder } = await import("./session-recorder-BMIJ3ZJM.js");
2024
+ const { SessionRecorder } = await import("./session-recorder-UTULJFTE.js");
2025
2025
  SessionRecorder.cleanup(session.name);
2026
2026
  } catch {
2027
2027
  }
@@ -1639,7 +1639,7 @@ var SessionRecorder = class _SessionRecorder {
1639
1639
  return this.actions.length;
1640
1640
  }
1641
1641
  /** Record an action triggered by a CDP command (e.g. xbrowser fill/click/goto) */
1642
- recordCommandAction(action) {
1642
+ async recordCommandAction(action) {
1643
1643
  const normalizedType = action.type === "cdp-fill" ? "input" : action.type === "cdp-click" ? "click" : action.type;
1644
1644
  const recent = this.actions[this.actions.length - 1];
1645
1645
  if (recent && Date.now() - recent.timestamp < 1500) {
@@ -1653,7 +1653,7 @@ var SessionRecorder = class _SessionRecorder {
1653
1653
  this.actionCounter++;
1654
1654
  const ts = Date.now();
1655
1655
  const actionUrl = action.url && action.url !== "about:blank" ? action.url : this.lastKnownUrl || this.page.url();
1656
- this.actions.push({
1656
+ const actionToPush = {
1657
1657
  id: this.actionCounter,
1658
1658
  type: action.type,
1659
1659
  timestamp: ts,
@@ -1661,6 +1661,11 @@ var SessionRecorder = class _SessionRecorder {
1661
1661
  pageTitle: "",
1662
1662
  element: action.element || (action.selector ? { tag: "", selector: action.selector, text: "" } : void 0),
1663
1663
  value: action.value
1664
+ };
1665
+ this.actions.push(actionToPush);
1666
+ actionToPush.elementScreenshot = await this.captureElementScreenshot(this.page, {
1667
+ type: action.type,
1668
+ element: actionToPush.element
1664
1669
  });
1665
1670
  this.lastActionTs = ts;
1666
1671
  this.cdpActionDedup = {
@@ -2319,6 +2324,8 @@ var SessionRecorder = class _SessionRecorder {
2319
2324
  clickContext,
2320
2325
  files: raw.files
2321
2326
  });
2327
+ const pushed = this.actions[this.actions.length - 1];
2328
+ pushed.elementScreenshot = await this.captureElementScreenshot(page, raw);
2322
2329
  this.lastActionTs = raw.ts;
2323
2330
  if (raw.type === "click" || raw.type === "navigate" || raw.type === "submit") {
2324
2331
  const detected = await this.detectCheckpoints(page);
@@ -2329,6 +2336,27 @@ var SessionRecorder = class _SessionRecorder {
2329
2336
  }
2330
2337
  }
2331
2338
  }
2339
+ /**
2340
+ * Capture a screenshot of the target element for key action types.
2341
+ * Returns a base64-encoded PNG string, or undefined if the element has no
2342
+ * selector or the screenshot fails (non-critical — never blocks recording).
2343
+ *
2344
+ * Only captures for: click, input, change, dblclick, and their CDP counterparts.
2345
+ */
2346
+ async captureElementScreenshot(page, action) {
2347
+ const keyTypes = ["click", "input", "change", "dblclick", "cdp-click", "cdp-fill", "filechooser"];
2348
+ if (!keyTypes.includes(action.type)) return;
2349
+ if (!action.element?.selector) return;
2350
+ try {
2351
+ const buffer = await page.locator(action.element.selector).first().screenshot({
2352
+ type: "png",
2353
+ timeout: 2e3
2354
+ });
2355
+ return buffer.toString("base64");
2356
+ } catch {
2357
+ return;
2358
+ }
2359
+ }
2332
2360
  /**
2333
2361
  * After a click, wait 300ms then scan for popover/dropdown/menu elements
2334
2362
  * near the click position. This runs server-side to avoid race conditions
@@ -1242,7 +1242,7 @@ var SessionRecorder = class _SessionRecorder {
1242
1242
  return this.actions.length;
1243
1243
  }
1244
1244
  /** Record an action triggered by a CDP command (e.g. xbrowser fill/click/goto) */
1245
- recordCommandAction(action) {
1245
+ async recordCommandAction(action) {
1246
1246
  const normalizedType = action.type === "cdp-fill" ? "input" : action.type === "cdp-click" ? "click" : action.type;
1247
1247
  const recent = this.actions[this.actions.length - 1];
1248
1248
  if (recent && Date.now() - recent.timestamp < 1500) {
@@ -1256,7 +1256,7 @@ var SessionRecorder = class _SessionRecorder {
1256
1256
  this.actionCounter++;
1257
1257
  const ts = Date.now();
1258
1258
  const actionUrl = action.url && action.url !== "about:blank" ? action.url : this.lastKnownUrl || this.page.url();
1259
- this.actions.push({
1259
+ const actionToPush = {
1260
1260
  id: this.actionCounter,
1261
1261
  type: action.type,
1262
1262
  timestamp: ts,
@@ -1264,6 +1264,11 @@ var SessionRecorder = class _SessionRecorder {
1264
1264
  pageTitle: "",
1265
1265
  element: action.element || (action.selector ? { tag: "", selector: action.selector, text: "" } : void 0),
1266
1266
  value: action.value
1267
+ };
1268
+ this.actions.push(actionToPush);
1269
+ actionToPush.elementScreenshot = await this.captureElementScreenshot(this.page, {
1270
+ type: action.type,
1271
+ element: actionToPush.element
1267
1272
  });
1268
1273
  this.lastActionTs = ts;
1269
1274
  this.cdpActionDedup = {
@@ -1922,6 +1927,8 @@ var SessionRecorder = class _SessionRecorder {
1922
1927
  clickContext,
1923
1928
  files: raw.files
1924
1929
  });
1930
+ const pushed = this.actions[this.actions.length - 1];
1931
+ pushed.elementScreenshot = await this.captureElementScreenshot(page, raw);
1925
1932
  this.lastActionTs = raw.ts;
1926
1933
  if (raw.type === "click" || raw.type === "navigate" || raw.type === "submit") {
1927
1934
  const detected = await this.detectCheckpoints(page);
@@ -1932,6 +1939,27 @@ var SessionRecorder = class _SessionRecorder {
1932
1939
  }
1933
1940
  }
1934
1941
  }
1942
+ /**
1943
+ * Capture a screenshot of the target element for key action types.
1944
+ * Returns a base64-encoded PNG string, or undefined if the element has no
1945
+ * selector or the screenshot fails (non-critical — never blocks recording).
1946
+ *
1947
+ * Only captures for: click, input, change, dblclick, and their CDP counterparts.
1948
+ */
1949
+ async captureElementScreenshot(page, action) {
1950
+ const keyTypes = ["click", "input", "change", "dblclick", "cdp-click", "cdp-fill", "filechooser"];
1951
+ if (!keyTypes.includes(action.type)) return;
1952
+ if (!action.element?.selector) return;
1953
+ try {
1954
+ const buffer = await page.locator(action.element.selector).first().screenshot({
1955
+ type: "png",
1956
+ timeout: 2e3
1957
+ });
1958
+ return buffer.toString("base64");
1959
+ } catch {
1960
+ return;
1961
+ }
1962
+ }
1935
1963
  /**
1936
1964
  * After a click, wait 300ms then scan for popover/dropdown/menu elements
1937
1965
  * near the click position. This runs server-side to avoid race conditions
@@ -4965,7 +4965,7 @@ async function closeSessionByName(name) {
4965
4965
  } catch {
4966
4966
  }
4967
4967
  try {
4968
- const { SessionRecorder } = await import("./session-recorder-33UKWCIR.js");
4968
+ const { SessionRecorder } = await import("./session-recorder-KPU4YQAE.js");
4969
4969
  SessionRecorder.cleanup(session.name);
4970
4970
  } catch {
4971
4971
  }
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  SessionRecorder
4
- } from "./chunk-RMYEHTLS.js";
4
+ } from "./chunk-YSCY52UJ.js";
5
5
  import {
6
6
  addKnownIssue,
7
7
  getKnowledgePath,
@@ -25,7 +25,7 @@ import {
25
25
  resolveLaunchOpts,
26
26
  saveSessionDiskMeta,
27
27
  setActivePage
28
- } from "./chunk-SJLRCE6N.js";
28
+ } from "./chunk-Z7WC4P2M.js";
29
29
  import "./chunk-TNEN6VQ2.js";
30
30
  import {
31
31
  forwardCommandLog,
@@ -7220,7 +7220,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
7220
7220
  }
7221
7221
  let targetPageOverride = null;
7222
7222
  if (_target && extraOpts?.cdpEndpoint) {
7223
- const { findTargetPage } = await import("./browser-XPKM344Y.js");
7223
+ const { findTargetPage } = await import("./browser-4KRHALJ3.js");
7224
7224
  targetPageOverride = await findTargetPage(extraOpts.cdpEndpoint, _target);
7225
7225
  if (!targetPageOverride) {
7226
7226
  return errorResult(`Target "${_target}" not found. Use 'xbrowser targets --cdp ${extraOpts.cdpEndpoint}' to list available pages.`);
@@ -11234,7 +11234,7 @@ async function handleFilter(args, _mode, options) {
11234
11234
  console.log(` Original: ${result.originalCount}, After: ${result.filteredCount}, Removed: ${result.removed} (${result.percentage}%)`);
11235
11235
  }
11236
11236
  async function handleGeneratePlugin(sessionName, pluginName, outputDir) {
11237
- const { SessionRecorder: SessionRecorder2 } = await import("./session-recorder-33UKWCIR.js");
11237
+ const { SessionRecorder: SessionRecorder2 } = await import("./session-recorder-KPU4YQAE.js");
11238
11238
  const { readSiteKnowledge: readSiteKnowledge2, toMarkdown } = await import("./site-knowledge-SYC6VCDB.js");
11239
11239
  const { mkdirSync: mkdirSync10, writeFileSync: writeFileSync12 } = await import("fs");
11240
11240
  const { join: join14 } = await import("path");
@@ -13196,7 +13196,7 @@ Run "xbrowser ${command} ${subCommand} --help" to see available parameters.`
13196
13196
  const targetPage = pages[cmdTabIndex];
13197
13197
  await targetPage.bringToFront().catch(() => {
13198
13198
  });
13199
- const { setActivePage: setActivePage2 } = await import("./browser-XPKM344Y.js");
13199
+ const { setActivePage: setActivePage2 } = await import("./browser-4KRHALJ3.js");
13200
13200
  setActivePage2(session, targetPage);
13201
13201
  }
13202
13202
  }
@@ -13469,7 +13469,7 @@ async function main() {
13469
13469
  const command = process.argv[2];
13470
13470
  const isLongRunning = command === "preview" || command === "serve";
13471
13471
  if (!isLongRunning) {
13472
- const { ensureProcessCanExit } = await import("./browser-XPKM344Y.js");
13472
+ const { ensureProcessCanExit } = await import("./browser-4KRHALJ3.js");
13473
13473
  await ensureProcessCanExit().catch(() => {
13474
13474
  });
13475
13475
  process.exit(exitCode);
@@ -5,7 +5,7 @@ import {
5
5
  } from "./chunk-VEDJ5XSQ.js";
6
6
  import {
7
7
  SessionRecorder
8
- } from "./chunk-FL6DOSWV.js";
8
+ } from "./chunk-TTZNR3QP.js";
9
9
  import {
10
10
  closeEphemeralContext,
11
11
  closeSessionByName,
@@ -21,7 +21,7 @@ import {
21
21
  resolveLaunchOpts,
22
22
  saveSessionDiskMeta,
23
23
  setActivePage
24
- } from "./chunk-HXLMMSU3.js";
24
+ } from "./chunk-OOJ2H7IL.js";
25
25
  import "./chunk-VJNMAWPZ.js";
26
26
  import "./chunk-TNEN6VQ2.js";
27
27
  import {
@@ -38,15 +38,13 @@ import {
38
38
  import "./chunk-KFQGP6VL.js";
39
39
 
40
40
  // src/daemon/daemon-main.ts
41
- import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync6, appendFileSync, unlinkSync } from "fs";
42
- import { join as join8 } from "path";
43
- import { homedir as homedir8 } from "os";
41
+ import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, appendFileSync, unlinkSync } from "fs";
42
+ import { join as join7 } from "path";
43
+ import { homedir as homedir7 } from "os";
44
44
  import { startHttpServer } from "@dyyz1993/xcli-core";
45
45
 
46
46
  // src/daemon/rpc-handlers.ts
47
- import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, readFileSync as readFileSync5 } from "fs";
48
- import { join as join7 } from "path";
49
- import { homedir as homedir7 } from "os";
47
+ import { readFileSync as readFileSync5 } from "fs";
50
48
  import {
51
49
  createSessionMeta,
52
50
  removeSession
@@ -6757,7 +6755,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
6757
6755
  }
6758
6756
  let targetPageOverride = null;
6759
6757
  if (_target && extraOpts?.cdpEndpoint) {
6760
- const { findTargetPage } = await import("./browser-LW2MDJE4.js");
6758
+ const { findTargetPage } = await import("./browser-6WJHQDPV.js");
6761
6759
  targetPageOverride = await findTargetPage(extraOpts.cdpEndpoint, _target);
6762
6760
  if (!targetPageOverride) {
6763
6761
  return errorResult(`Target "${_target}" not found. Use 'xbrowser targets --cdp ${extraOpts.cdpEndpoint}' to list available pages.`);
@@ -7795,60 +7793,6 @@ var PlaybackEngine = class _PlaybackEngine {
7795
7793
  // src/daemon/rpc-handlers.ts
7796
7794
  var activeRecorders = /* @__PURE__ */ new Map();
7797
7795
  var replayResumeResolvers = /* @__PURE__ */ new Map();
7798
- var CONFIG_DIR3 = join7(homedir7(), ".xbrowser");
7799
- var RECORDING_INJECT_JS = `
7800
- (function(){
7801
- if(window.__xb_rec) return;
7802
- window.__xb_rec = true;
7803
- window.__xb_evts = [];
7804
- window.__xb_t0 = Date.now();
7805
- function d(el){
7806
- if(!el||!el.tagName) return {tag:'unknown'};
7807
- var o={tag:el.tagName.toLowerCase(),text:(el.textContent||'').trim().substring(0,80)};
7808
- if(el.getAttribute('role')) o.role=el.getAttribute('role');
7809
- if(el.id) o.id=el.id;
7810
- if(el.getAttribute('type')) o.type=el.getAttribute('type');
7811
- if(el.getAttribute('placeholder')) o.placeholder=el.getAttribute('placeholder');
7812
- if(el.getAttribute('aria-label')) o.ariaLabel=el.getAttribute('aria-label');
7813
- if(el.contentEditable==='true') o.contentEditable=true;
7814
- return o;
7815
- }
7816
- function p(t,det){
7817
- var e={type:t,ts:Date.now()-window.__xb_t0,url:location.href};
7818
- for(var k in det) e[k]=det[k];
7819
- window.__xb_evts.push(e);
7820
- }
7821
- document.addEventListener('click',function(e){p('click',{target:d(e.target),x:e.clientX,y:e.clientY})},true);
7822
- document.addEventListener('dblclick',function(e){p('dblclick',{target:d(e.target),x:e.clientX,y:e.clientY})},true);
7823
- document.addEventListener('input',function(e){var el=e.target;p('input',{target:d(el),value:(el.value||el.textContent||'').substring(0,200)})},true);
7824
- document.addEventListener('change',function(e){p('change',{target:d(e.target),value:(e.target.value||'').substring(0,100)})},true);
7825
- document.addEventListener('keydown',function(e){if(e.key==='Enter'||e.key==='Tab'||e.key==='Escape'||e.key.startsWith('Arrow'))p('keydown',{key:e.key,target:d(e.target)})},true);
7826
- document.addEventListener('submit',function(e){p('submit',{target:d(e.target)})},true);
7827
- var __xb_last_focus=null;document.addEventListener('focus',function(e){var t=e.target.tagName;if(t==='INPUT'||t==='TEXTAREA'||e.target.contentEditable==='true'){var sel=e.target.id||e.target.name||e.target.placeholder;if(sel===__xb_last_focus)return;__xb_last_focus=sel;p('input_focused',{target:d(e.target)})}},true);
7828
- var obs=new MutationObserver(function(mutations){
7829
- for(var m of mutations){
7830
- for(var node of m.addedNodes){
7831
- if(node.nodeType===1&&node.tagName){
7832
- var text=(node.textContent||'').trim().substring(0,60);
7833
- if(text&&text.length>1) p('dom_added',{tag:node.tagName.toLowerCase(),role:node.getAttribute&&node.getAttribute('role'),text:text});
7834
- }
7835
- }
7836
- }
7837
- });
7838
- if(document.body) obs.observe(document.body,{childList:true,subtree:true});
7839
- p('recording_started',{url:location.href});
7840
- })();
7841
- `;
7842
- async function injectRecording(page) {
7843
- try {
7844
- await page.evaluate(RECORDING_INJECT_JS);
7845
- } catch {
7846
- }
7847
- try {
7848
- await page.addInitScript(RECORDING_INJECT_JS);
7849
- } catch {
7850
- }
7851
- }
7852
7796
  function createRPCHandler() {
7853
7797
  let previewWS = null;
7854
7798
  const INTERACTION_COMMANDS2 = /* @__PURE__ */ new Set([
@@ -7913,15 +7857,6 @@ function createRPCHandler() {
7913
7857
  return handleNetworkFeedback(params);
7914
7858
  case "network:export":
7915
7859
  return handleNetworkExport(params);
7916
- // ── Recording ──
7917
- case "recording:status":
7918
- return handleRecordingStatus(params);
7919
- case "recording:events":
7920
- return handleRecordingEvents(params);
7921
- case "recording:clear":
7922
- return handleRecordingClear(params);
7923
- case "recording:save":
7924
- return handleRecordingSave(params);
7925
7860
  // ── Command log ──
7926
7861
  case "command:log":
7927
7862
  return handleCommandLog(params);
@@ -7988,7 +7923,6 @@ function createRPCHandler() {
7988
7923
  session = await createSession(name, url);
7989
7924
  }
7990
7925
  }
7991
- await injectRecording(session.page);
7992
7926
  if (previewWS) previewWS.registerSession(session.name, session.page);
7993
7927
  saveSessionDiskMeta(name, {
7994
7928
  id: session.id,
@@ -8209,61 +8143,6 @@ function createRPCHandler() {
8209
8143
  if (!entry.capture) return { error: `Entry #${id} not found` };
8210
8144
  return exportEntry(entry.capture, lang);
8211
8145
  }
8212
- async function handleRecordingStatus(params) {
8213
- const sess = findSession(params.session || "default");
8214
- if (!sess) return { recording: false, error: "No session" };
8215
- try {
8216
- const result = await sess.page.evaluate(() => ({
8217
- active: !!window.__xb_rec,
8218
- events: window.__xb_evts?.length || 0,
8219
- url: location.href
8220
- }));
8221
- return { recording: true, ...result };
8222
- } catch {
8223
- return { recording: false, error: "Page unreachable" };
8224
- }
8225
- }
8226
- async function handleRecordingEvents(params) {
8227
- const sess = findSession(params.session || "default");
8228
- if (!sess) return { events: [], error: "No session" };
8229
- try {
8230
- const events = await sess.page.evaluate(() => window.__xb_evts || []);
8231
- return { events, url: sess.page.url() };
8232
- } catch {
8233
- return { events: [], error: "Page unreachable" };
8234
- }
8235
- }
8236
- async function handleRecordingClear(params) {
8237
- const sess = findSession(params.session || "default");
8238
- if (!sess) return { ok: false, error: "No session" };
8239
- try {
8240
- await sess.page.evaluate(() => {
8241
- window.__xb_evts = [];
8242
- window.__xb_t0 = Date.now();
8243
- });
8244
- return { ok: true };
8245
- } catch {
8246
- return { ok: false, error: "Page unreachable" };
8247
- }
8248
- }
8249
- async function handleRecordingSave(params) {
8250
- const sess = findSession(params.session || "default");
8251
- if (!sess) return { ok: false, error: "No session" };
8252
- try {
8253
- const events = await sess.page.evaluate(() => window.__xb_evts || []);
8254
- const recordingsDir = join7(CONFIG_DIR3, "recordings");
8255
- mkdirSync5(recordingsDir, { recursive: true });
8256
- const outPath = params.path || join7(recordingsDir, `recording-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.json`);
8257
- writeFileSync5(outPath, JSON.stringify({
8258
- startUrl: sess.page.url(),
8259
- recordedAt: (/* @__PURE__ */ new Date()).toISOString(),
8260
- events
8261
- }, null, 2));
8262
- return { ok: true, path: outPath, events: events.length };
8263
- } catch (e) {
8264
- return { ok: false, error: errMsg(e) };
8265
- }
8266
- }
8267
8146
  function handleCommandLog(params) {
8268
8147
  const sessionName = params.session || "default";
8269
8148
  const limit = params.limit || 50;
@@ -8301,7 +8180,7 @@ function createRPCHandler() {
8301
8180
  } catch {
8302
8181
  }
8303
8182
  }
8304
- recorder.recordCommandAction({
8183
+ await recorder.recordCommandAction({
8305
8184
  type: actionType,
8306
8185
  selector,
8307
8186
  value,
@@ -8324,7 +8203,6 @@ function createRPCHandler() {
8324
8203
  try {
8325
8204
  const sessionOpts = cdpEndpoint ? { cdpEndpoint } : void 0;
8326
8205
  session = await createSession(sessionName, url, sessionOpts);
8327
- await injectRecording(session.page);
8328
8206
  if (previewWS) previewWS.registerSession(session.name, session.page);
8329
8207
  saveSessionDiskMeta(sessionName, {
8330
8208
  id: session.id,
@@ -8360,8 +8238,8 @@ function createRPCHandler() {
8360
8238
  const existingData = SessionRecorder.readData(sessionName);
8361
8239
  if (existingData) {
8362
8240
  if (outputPath) {
8363
- const { writeFileSync: writeFileSync7 } = await import("fs");
8364
- writeFileSync7(outputPath, JSON.stringify(existingData, null, 2), "utf-8");
8241
+ const { writeFileSync: writeFileSync6 } = await import("fs");
8242
+ writeFileSync6(outputPath, JSON.stringify(existingData, null, 2), "utf-8");
8365
8243
  }
8366
8244
  return {
8367
8245
  ok: true,
@@ -8378,14 +8256,14 @@ function createRPCHandler() {
8378
8256
  const { data, summary } = await recorder.stop();
8379
8257
  activeRecorders.delete(sessionName);
8380
8258
  if (outputPath) {
8381
- const { writeFileSync: writeFileSync7, mkdirSync: mkdirSync7 } = await import("fs");
8259
+ const { writeFileSync: writeFileSync6, mkdirSync: mkdirSync6 } = await import("fs");
8382
8260
  const { dirname: dirname2 } = await import("path");
8383
- mkdirSync7(dirname2(outputPath), { recursive: true });
8261
+ mkdirSync6(dirname2(outputPath), { recursive: true });
8384
8262
  if (outputPath.endsWith(".yaml") || outputPath.endsWith(".yml")) {
8385
8263
  const yaml2 = (await import("yaml")).default;
8386
- writeFileSync7(outputPath, yaml2.stringify(data), "utf-8");
8264
+ writeFileSync6(outputPath, yaml2.stringify(data), "utf-8");
8387
8265
  } else {
8388
- writeFileSync7(outputPath, JSON.stringify(data, null, 2), "utf-8");
8266
+ writeFileSync6(outputPath, JSON.stringify(data, null, 2), "utf-8");
8389
8267
  }
8390
8268
  }
8391
8269
  return {
@@ -9594,12 +9472,12 @@ var FileListHandler = class {
9594
9472
  const msg = ctx.message;
9595
9473
  try {
9596
9474
  const { readdirSync, statSync } = await import("fs");
9597
- const { join: join9, resolve } = await import("path");
9475
+ const { join: join8, resolve } = await import("path");
9598
9476
  const targetPath = resolve(msg.path);
9599
9477
  const entries = readdirSync(targetPath);
9600
9478
  const files = entries.map((name) => {
9601
9479
  try {
9602
- const stat = statSync(join9(targetPath, name));
9480
+ const stat = statSync(join8(targetPath, name));
9603
9481
  return { name, isDir: stat.isDirectory(), size: stat.size, modified: stat.mtime.toISOString() };
9604
9482
  } catch {
9605
9483
  return { name, isDir: false, size: 0, modified: "" };
@@ -11260,8 +11138,8 @@ connectWS();
11260
11138
  }
11261
11139
 
11262
11140
  // src/daemon/daemon-main.ts
11263
- var CONFIG_DIR4 = join8(homedir8(), ".xbrowser");
11264
- var LOG_FILE = join8(CONFIG_DIR4, "daemon.log");
11141
+ var CONFIG_DIR3 = join7(homedir7(), ".xbrowser");
11142
+ var LOG_FILE = join7(CONFIG_DIR3, "daemon.log");
11265
11143
  function log(msg) {
11266
11144
  const ts = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").substring(0, 19);
11267
11145
  const line = `[DAEMON ${ts}] ${msg}
@@ -11293,7 +11171,7 @@ async function main() {
11293
11171
  if (err.code === "EADDRINUSE") {
11294
11172
  log(`Port ${daemonPort} already in use \u2014 another daemon instance likely won the startup race. Exiting gracefully.`);
11295
11173
  try {
11296
- unlinkSync(join8(CONFIG_DIR4, "daemon.json"));
11174
+ unlinkSync(join7(CONFIG_DIR3, "daemon.json"));
11297
11175
  } catch {
11298
11176
  }
11299
11177
  process.exit(0);
@@ -11327,8 +11205,8 @@ async function main() {
11327
11205
  rpcHandler.setPreviewWS(previewWS);
11328
11206
  previewWS.on("screencast-started", (sid) => log(`Preview screencast started: ${sid}`));
11329
11207
  previewWS.on("screencast-stopped", (sid) => log(`Preview screencast stopped: ${sid}`));
11330
- mkdirSync6(CONFIG_DIR4, { recursive: true });
11331
- writeFileSync6(join8(CONFIG_DIR4, "daemon.json"), JSON.stringify({
11208
+ mkdirSync5(CONFIG_DIR3, { recursive: true });
11209
+ writeFileSync5(join7(CONFIG_DIR3, "daemon.json"), JSON.stringify({
11332
11210
  port: daemonPort,
11333
11211
  pid: process.pid,
11334
11212
  startedAt: Date.now()
package/dist/index.d.ts CHANGED
@@ -1737,6 +1737,8 @@ interface UserAction {
1737
1737
  distance: number;
1738
1738
  duration: number;
1739
1739
  };
1740
+ /** Base64-encoded PNG screenshot of the target element (captured on key actions) */
1741
+ elementScreenshot?: string;
1740
1742
  }
1741
1743
  interface NetworkEntry {
1742
1744
  id: number;
@@ -1854,7 +1856,7 @@ declare class SessionRecorder {
1854
1856
  value?: string;
1855
1857
  url?: string;
1856
1858
  element?: UserAction['element'];
1857
- }): void;
1859
+ }): Promise<void>;
1858
1860
  get networkCount(): number;
1859
1861
  getLiveData(): RecordingData;
1860
1862
  addManualCheckpoint(type: string, hint: string, selector?: string): CheckpointEntry;
@@ -1884,6 +1886,14 @@ declare class SessionRecorder {
1884
1886
  private handleFileChooser;
1885
1887
  private pollActions;
1886
1888
  private flushPendingActions;
1889
+ /**
1890
+ * Capture a screenshot of the target element for key action types.
1891
+ * Returns a base64-encoded PNG string, or undefined if the element has no
1892
+ * selector or the screenshot fails (non-critical — never blocks recording).
1893
+ *
1894
+ * Only captures for: click, input, change, dblclick, and their CDP counterparts.
1895
+ */
1896
+ private captureElementScreenshot;
1887
1897
  /**
1888
1898
  * After a click, wait 300ms then scan for popover/dropdown/menu elements
1889
1899
  * near the click position. This runs server-side to avoid race conditions
package/dist/index.js CHANGED
@@ -57,7 +57,7 @@ import {
57
57
  } from "./chunk-GJAV3QGG.js";
58
58
  import {
59
59
  SessionRecorder
60
- } from "./chunk-RMYEHTLS.js";
60
+ } from "./chunk-YSCY52UJ.js";
61
61
  import {
62
62
  addKnownIssue,
63
63
  getKnowledgePath,
@@ -82,7 +82,7 @@ import {
82
82
  resolveLaunchOpts,
83
83
  saveSessionDiskMeta,
84
84
  setActivePage
85
- } from "./chunk-CJJ564VK.js";
85
+ } from "./chunk-MSQIPXKE.js";
86
86
  import "./chunk-VJNMAWPZ.js";
87
87
  import "./chunk-TNEN6VQ2.js";
88
88
  import {
@@ -7540,7 +7540,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
7540
7540
  }
7541
7541
  let targetPageOverride = null;
7542
7542
  if (_target && extraOpts?.cdpEndpoint) {
7543
- const { findTargetPage } = await import("./browser-TLIDFFEG.js");
7543
+ const { findTargetPage } = await import("./browser-C63PFGPT.js");
7544
7544
  targetPageOverride = await findTargetPage(extraOpts.cdpEndpoint, _target);
7545
7545
  if (!targetPageOverride) {
7546
7546
  return errorResult(`Target "${_target}" not found. Use 'xbrowser targets --cdp ${extraOpts.cdpEndpoint}' to list available pages.`);
@@ -11557,7 +11557,7 @@ async function handleFilter(args, _mode, options) {
11557
11557
  console.log(` Original: ${result.originalCount}, After: ${result.filteredCount}, Removed: ${result.removed} (${result.percentage}%)`);
11558
11558
  }
11559
11559
  async function handleGeneratePlugin(sessionName, pluginName, outputDir) {
11560
- const { SessionRecorder: SessionRecorder2 } = await import("./session-recorder-33UKWCIR.js");
11560
+ const { SessionRecorder: SessionRecorder2 } = await import("./session-recorder-KPU4YQAE.js");
11561
11561
  const { readSiteKnowledge: readSiteKnowledge2, toMarkdown } = await import("./site-knowledge-SYC6VCDB.js");
11562
11562
  const { mkdirSync: mkdirSync11, writeFileSync: writeFileSync13 } = await import("fs");
11563
11563
  const { join: join14 } = await import("path");
@@ -13519,7 +13519,7 @@ Run "xbrowser ${command} ${subCommand} --help" to see available parameters.`
13519
13519
  const targetPage = pages[cmdTabIndex];
13520
13520
  await targetPage.bringToFront().catch(() => {
13521
13521
  });
13522
- const { setActivePage: setActivePage2 } = await import("./browser-TLIDFFEG.js");
13522
+ const { setActivePage: setActivePage2 } = await import("./browser-C63PFGPT.js");
13523
13523
  setActivePage2(session, targetPage);
13524
13524
  }
13525
13525
  }
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  SessionRecorder
3
- } from "./chunk-RMYEHTLS.js";
3
+ } from "./chunk-YSCY52UJ.js";
4
4
  import "./chunk-OZKD3W4X.js";
5
5
  import "./chunk-KFQGP6VL.js";
6
6
  export {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  SessionRecorder
3
- } from "./chunk-FL6DOSWV.js";
3
+ } from "./chunk-TTZNR3QP.js";
4
4
  import "./chunk-KFQGP6VL.js";
5
5
  export {
6
6
  SessionRecorder
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xbrowser/cli",
3
- "version": "1.8.7",
3
+ "version": "1.9.0",
4
4
  "description": "Browser automation CLI for web scraping, headless browsing, SEO analysis, and AI agent workflows. A command-line alternative to Playwright, Puppeteer, and Selenium.",
5
5
  "type": "module",
6
6
  "bin": {