@xbrowser/cli 1.8.6 → 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.
@@ -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
@@ -55,7 +53,7 @@ import {
55
53
  // src/executor.ts
56
54
  import {
57
55
  ok as ok25,
58
- fail as fail8,
56
+ fail as fail9,
59
57
  isCommandResult,
60
58
  CompositeStorage as CompositeStorage2,
61
59
  TipCollector as TipCollector2,
@@ -2283,7 +2281,7 @@ var scrapeCommand = registerCommand({
2283
2281
  scope: "project",
2284
2282
  selectorParams: ["selector"],
2285
2283
  parameters: z15.object({
2286
- url: z15.string(),
2284
+ url: z15.string().optional(),
2287
2285
  selector: z15.string().optional(),
2288
2286
  timeout: z15.number().default(3e4),
2289
2287
  format: z15.enum(["markdown", "html", "text"]).default("markdown"),
@@ -2296,10 +2294,14 @@ var scrapeCommand = registerCommand({
2296
2294
  const { context, page } = await createEphemeralContext(resolveLaunchOpts(ctx));
2297
2295
  const maxAttempts = p.retries + 1;
2298
2296
  try {
2297
+ const targetUrl = p.url || page.url();
2298
+ if (!targetUrl || targetUrl === "about:blank") {
2299
+ return fail3("URL is required. Provide a URL or connect to a browser (--cdp) with a non-blank page.");
2300
+ }
2299
2301
  let lastError;
2300
2302
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
2301
2303
  try {
2302
- await page.goto(p.url, { waitUntil: "commit", timeout: p.timeout });
2304
+ await page.goto(targetUrl, { waitUntil: "commit", timeout: p.timeout });
2303
2305
  await page.waitForSelector("body", { timeout: p.timeout }).catch(() => {
2304
2306
  });
2305
2307
  await page.waitForLoadState("networkidle", Math.min(p.timeout, 8e3)).catch(() => {
@@ -2360,7 +2362,7 @@ var scrapeCommand = registerCommand({
2360
2362
  return { url: location.href, title: document.title, navigation, tables, forms: forms.slice(0, 20), links: links.slice(0, 30), mainText };
2361
2363
  });
2362
2364
  try {
2363
- persistFromScrape(p.url, structured);
2365
+ persistFromScrape(targetUrl, structured);
2364
2366
  } catch {
2365
2367
  }
2366
2368
  if (p.mode === "smart") {
@@ -2461,7 +2463,7 @@ var scrapeCommand = registerCommand({
2461
2463
 
2462
2464
  // src/commands/map.ts
2463
2465
  import { z as z16 } from "zod";
2464
- import { ok as ok16 } from "@dyyz1993/xcli-core";
2466
+ import { ok as ok16, fail as fail4 } from "@dyyz1993/xcli-core";
2465
2467
 
2466
2468
  // src/utils/url.ts
2467
2469
  var SKIP_EXTENSIONS = /* @__PURE__ */ new Set([
@@ -2697,7 +2699,7 @@ var mapCommand = registerCommand({
2697
2699
  description: "Discover all URLs on a website via sitemap and page link extraction",
2698
2700
  scope: "project",
2699
2701
  parameters: z16.object({
2700
- url: z16.string(),
2702
+ url: z16.string().optional(),
2701
2703
  search: z16.string().optional(),
2702
2704
  sitemap: z16.enum(["include", "only"]).optional(),
2703
2705
  includeSubdomains: z16.boolean().optional(),
@@ -2708,7 +2710,11 @@ var mapCommand = registerCommand({
2708
2710
  handler: async (p, ctx) => {
2709
2711
  const { context, page } = await createEphemeralContext(resolveLaunchOpts(ctx));
2710
2712
  try {
2711
- const links = await discoverUrls(page, p.url, {
2713
+ const targetUrl = p.url || page.url();
2714
+ if (!targetUrl || targetUrl === "about:blank") {
2715
+ return fail4("URL is required. Provide a URL or connect to a browser (--cdp) with a non-blank page.");
2716
+ }
2717
+ const links = await discoverUrls(page, targetUrl, {
2712
2718
  sitemap: p.sitemap,
2713
2719
  includeSubdomains: p.includeSubdomains,
2714
2720
  allowExternalLinks: p.allowExternalLinks,
@@ -3551,7 +3557,7 @@ ${errors.map((e) => ` - ${e.engine}: ${e.error}`).join("\n")}`
3551
3557
 
3552
3558
  // src/commands/network.ts
3553
3559
  import { z as z19 } from "zod";
3554
- import { ok as ok19, fail as fail4 } from "@dyyz1993/xcli-core";
3560
+ import { ok as ok19, fail as fail5 } from "@dyyz1993/xcli-core";
3555
3561
  function extractPath2(url) {
3556
3562
  try {
3557
3563
  const u = new URL(url);
@@ -3716,7 +3722,7 @@ var networkCommand = registerCommand({
3716
3722
  };
3717
3723
  if (p.listen) {
3718
3724
  const page2 = ctx.page;
3719
- if (!page2) return fail4("No active page. Use --cdp to connect first.");
3725
+ if (!page2) return fail5("No active page. Use --cdp to connect first.");
3720
3726
  const captures = [];
3721
3727
  const consoleMessages = [];
3722
3728
  const wsCaptures = [];
@@ -4096,7 +4102,7 @@ var ENGINE_KEY_ENUM = z20.enum(ALL_ENGINE_KEYS);
4096
4102
 
4097
4103
  // src/commands/snapshot.ts
4098
4104
  import { z as z21 } from "zod";
4099
- import { ok as ok20, fail as fail5, normalizeTips as normalizeTips3 } from "@dyyz1993/xcli-core";
4105
+ import { ok as ok20, fail as fail6, normalizeTips as normalizeTips3 } from "@dyyz1993/xcli-core";
4100
4106
 
4101
4107
  // src/runtime/ref-store.ts
4102
4108
  var sessions = /* @__PURE__ */ new Map();
@@ -5067,7 +5073,7 @@ var snapshotCommand = registerCommand({
5067
5073
  persistSemantics(url, aria);
5068
5074
  return ok20({ url, title, aria, text, dom }, normalizeTips3(tips));
5069
5075
  }
5070
- return fail5(`Unknown snapshot type: ${p.type}`);
5076
+ return fail6(`Unknown snapshot type: ${p.type}`);
5071
5077
  }
5072
5078
  });
5073
5079
  function persistSemantics(url, aria) {
@@ -5236,7 +5242,7 @@ var waitForCommand = registerCommand({
5236
5242
 
5237
5243
  // src/commands/tab.ts
5238
5244
  import { z as z23 } from "zod";
5239
- import { ok as ok22, fail as fail6 } from "@dyyz1993/xcli-core";
5245
+ import { ok as ok22, fail as fail7 } from "@dyyz1993/xcli-core";
5240
5246
  var TabParams = z23.object({
5241
5247
  subcommand: z23.enum(["list", "new", "close", "switch"]),
5242
5248
  url: z23.string().optional(),
@@ -5253,7 +5259,7 @@ var tabCommand = registerCommand({
5253
5259
  }),
5254
5260
  handler: async (p, ctx) => {
5255
5261
  if (!ctx.browserContext) {
5256
- return fail6("No browser context available. Use --cdp to connect to a browser first.");
5262
+ return fail7("No browser context available. Use --cdp to connect to a browser first.");
5257
5263
  }
5258
5264
  const pages = ctx.browserContext.pages();
5259
5265
  switch (p.subcommand) {
@@ -5266,7 +5272,7 @@ var tabCommand = registerCommand({
5266
5272
  case "switch":
5267
5273
  return handleSwitch(p, pages, ctx);
5268
5274
  default:
5269
- return fail6(`Unknown subcommand: ${p.subcommand}`);
5275
+ return fail7(`Unknown subcommand: ${p.subcommand}`);
5270
5276
  }
5271
5277
  }
5272
5278
  });
@@ -5323,11 +5329,11 @@ async function handleNew(p, _pages, ctx) {
5323
5329
  }
5324
5330
  async function handleClose(p, pages, ctx) {
5325
5331
  if (pages.length <= 1) {
5326
- return fail6("Cannot close the last remaining tab");
5332
+ return fail7("Cannot close the last remaining tab");
5327
5333
  }
5328
5334
  const closeIndex = p.index ?? pages.indexOf(ctx.page);
5329
5335
  if (closeIndex < 0 || closeIndex >= pages.length) {
5330
- return fail6(`Invalid tab index: ${closeIndex}. Valid range: 0-${pages.length - 1}`);
5336
+ return fail7(`Invalid tab index: ${closeIndex}. Valid range: 0-${pages.length - 1}`);
5331
5337
  }
5332
5338
  const pageToClose = pages[closeIndex];
5333
5339
  const isActivePage = pageToClose === ctx.page;
@@ -5350,10 +5356,10 @@ async function handleClose(p, pages, ctx) {
5350
5356
  }
5351
5357
  async function handleSwitch(p, pages, ctx) {
5352
5358
  if (p.index === void 0) {
5353
- return fail6("Parameter --index is required for switch subcommand");
5359
+ return fail7("Parameter --index is required for switch subcommand");
5354
5360
  }
5355
5361
  if (p.index < 0 || p.index >= pages.length) {
5356
- return fail6(`Invalid tab index: ${p.index}. Valid range: 0-${pages.length - 1}`);
5362
+ return fail7(`Invalid tab index: ${p.index}. Valid range: 0-${pages.length - 1}`);
5357
5363
  }
5358
5364
  const targetPage = pages[p.index];
5359
5365
  await targetPage.bringToFront().catch(() => {
@@ -5605,7 +5611,7 @@ registerCommandDefinition("addinitscript", ["script"]);
5605
5611
 
5606
5612
  // src/commands/find.ts
5607
5613
  import { z as z25 } from "zod";
5608
- import { ok as ok24, fail as fail7, normalizeTips as normalizeTips5 } from "@dyyz1993/xcli-core";
5614
+ import { ok as ok24, fail as fail8, normalizeTips as normalizeTips5 } from "@dyyz1993/xcli-core";
5609
5615
  var actionSchema2 = z25.enum(["click", "fill", "type", "select", "hover", "check"]);
5610
5616
  var findCommand = registerCommand({
5611
5617
  name: "find",
@@ -5646,7 +5652,7 @@ var findCommand = registerCommand({
5646
5652
  });
5647
5653
  const count = await locator.count();
5648
5654
  if (count === 0) {
5649
- return fail7(`No element found with ${p.strategy}="${p.value}"`);
5655
+ return fail8(`No element found with ${p.strategy}="${p.value}"`);
5650
5656
  }
5651
5657
  const tips = [];
5652
5658
  const target = selectTarget(locator, p.strategy);
@@ -5658,15 +5664,15 @@ var findCommand = registerCommand({
5658
5664
  await target.click({ timeout: p.timeout, force: true });
5659
5665
  return okWithTips({ matched: count, selector, action: "click" }, tips);
5660
5666
  } else if (actionName === "fill") {
5661
- if (actionValue === void 0) return fail7("find fill requires a value");
5667
+ if (actionValue === void 0) return fail8("find fill requires a value");
5662
5668
  await target.fill(actionValue, { timeout: p.timeout, force: true });
5663
5669
  return okWithTips({ matched: count, selector, action: `fill("${actionValue}")` }, tips);
5664
5670
  } else if (actionName === "type") {
5665
- if (actionValue === void 0) return fail7("find type requires a value");
5671
+ if (actionValue === void 0) return fail8("find type requires a value");
5666
5672
  await target.type(actionValue, { delay: 10, timeout: p.timeout });
5667
5673
  return okWithTips({ matched: count, selector, action: `type("${actionValue}")` }, tips);
5668
5674
  } else if (actionName === "select") {
5669
- if (actionValue === void 0) return fail7("find select requires a value");
5675
+ if (actionValue === void 0) return fail8("find select requires a value");
5670
5676
  await target.selectOption(actionValue);
5671
5677
  return okWithTips({ matched: count, selector, action: `select("${actionValue}")` }, tips);
5672
5678
  } else if (actionName === "hover") {
@@ -6672,7 +6678,7 @@ async function guardCheck(commandName) {
6672
6678
  }
6673
6679
  }
6674
6680
  function errorResult(message) {
6675
- return { ...fail8(message), duration: 0 };
6681
+ return { ...fail9(message), duration: 0 };
6676
6682
  }
6677
6683
  function tipsToMessages(tips) {
6678
6684
  if (!tips || tips.length === 0) return [];
@@ -6749,7 +6755,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
6749
6755
  }
6750
6756
  let targetPageOverride = null;
6751
6757
  if (_target && extraOpts?.cdpEndpoint) {
6752
- const { findTargetPage } = await import("./browser-LW2MDJE4.js");
6758
+ const { findTargetPage } = await import("./browser-6WJHQDPV.js");
6753
6759
  targetPageOverride = await findTargetPage(extraOpts.cdpEndpoint, _target);
6754
6760
  if (!targetPageOverride) {
6755
6761
  return errorResult(`Target "${_target}" not found. Use 'xbrowser targets --cdp ${extraOpts.cdpEndpoint}' to list available pages.`);
@@ -6973,7 +6979,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
6973
6979
  duration,
6974
6980
  timestamp: start
6975
6981
  });
6976
- return { ...fail8(errorMessage), duration };
6982
+ return { ...fail9(errorMessage), duration };
6977
6983
  } finally {
6978
6984
  }
6979
6985
  }
@@ -7014,7 +7020,7 @@ async function executeChain(input, options) {
7014
7020
  results.push({
7015
7021
  command: cmdName,
7016
7022
  raw: cmdStr,
7017
- ...fail8(`Plugin "${cmdName}" requires a sub-command`),
7023
+ ...fail9(`Plugin "${cmdName}" requires a sub-command`),
7018
7024
  duration: 0
7019
7025
  });
7020
7026
  if (type === "and") {
@@ -7033,7 +7039,7 @@ async function executeChain(input, options) {
7033
7039
  results.push({
7034
7040
  command: cmdName,
7035
7041
  raw: cmdStr,
7036
- ...fail8(`Unknown command "${subCommand}" for plugin "${cmdName}"`),
7042
+ ...fail9(`Unknown command "${subCommand}" for plugin "${cmdName}"`),
7037
7043
  duration: 0
7038
7044
  });
7039
7045
  if (type === "and") {
@@ -7165,7 +7171,7 @@ async function executeChain(input, options) {
7165
7171
  results.push({
7166
7172
  command: `${cmdName} ${subCommand}`,
7167
7173
  raw: cmdStr,
7168
- ...fail8(errorMessage),
7174
+ ...fail9(errorMessage),
7169
7175
  duration: duration2
7170
7176
  });
7171
7177
  if (type === "and") {
@@ -7787,60 +7793,6 @@ var PlaybackEngine = class _PlaybackEngine {
7787
7793
  // src/daemon/rpc-handlers.ts
7788
7794
  var activeRecorders = /* @__PURE__ */ new Map();
7789
7795
  var replayResumeResolvers = /* @__PURE__ */ new Map();
7790
- var CONFIG_DIR3 = join7(homedir7(), ".xbrowser");
7791
- var RECORDING_INJECT_JS = `
7792
- (function(){
7793
- if(window.__xb_rec) return;
7794
- window.__xb_rec = true;
7795
- window.__xb_evts = [];
7796
- window.__xb_t0 = Date.now();
7797
- function d(el){
7798
- if(!el||!el.tagName) return {tag:'unknown'};
7799
- var o={tag:el.tagName.toLowerCase(),text:(el.textContent||'').trim().substring(0,80)};
7800
- if(el.getAttribute('role')) o.role=el.getAttribute('role');
7801
- if(el.id) o.id=el.id;
7802
- if(el.getAttribute('type')) o.type=el.getAttribute('type');
7803
- if(el.getAttribute('placeholder')) o.placeholder=el.getAttribute('placeholder');
7804
- if(el.getAttribute('aria-label')) o.ariaLabel=el.getAttribute('aria-label');
7805
- if(el.contentEditable==='true') o.contentEditable=true;
7806
- return o;
7807
- }
7808
- function p(t,det){
7809
- var e={type:t,ts:Date.now()-window.__xb_t0,url:location.href};
7810
- for(var k in det) e[k]=det[k];
7811
- window.__xb_evts.push(e);
7812
- }
7813
- document.addEventListener('click',function(e){p('click',{target:d(e.target),x:e.clientX,y:e.clientY})},true);
7814
- document.addEventListener('dblclick',function(e){p('dblclick',{target:d(e.target),x:e.clientX,y:e.clientY})},true);
7815
- document.addEventListener('input',function(e){var el=e.target;p('input',{target:d(el),value:(el.value||el.textContent||'').substring(0,200)})},true);
7816
- document.addEventListener('change',function(e){p('change',{target:d(e.target),value:(e.target.value||'').substring(0,100)})},true);
7817
- 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);
7818
- document.addEventListener('submit',function(e){p('submit',{target:d(e.target)})},true);
7819
- 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);
7820
- var obs=new MutationObserver(function(mutations){
7821
- for(var m of mutations){
7822
- for(var node of m.addedNodes){
7823
- if(node.nodeType===1&&node.tagName){
7824
- var text=(node.textContent||'').trim().substring(0,60);
7825
- if(text&&text.length>1) p('dom_added',{tag:node.tagName.toLowerCase(),role:node.getAttribute&&node.getAttribute('role'),text:text});
7826
- }
7827
- }
7828
- }
7829
- });
7830
- if(document.body) obs.observe(document.body,{childList:true,subtree:true});
7831
- p('recording_started',{url:location.href});
7832
- })();
7833
- `;
7834
- async function injectRecording(page) {
7835
- try {
7836
- await page.evaluate(RECORDING_INJECT_JS);
7837
- } catch {
7838
- }
7839
- try {
7840
- await page.addInitScript(RECORDING_INJECT_JS);
7841
- } catch {
7842
- }
7843
- }
7844
7796
  function createRPCHandler() {
7845
7797
  let previewWS = null;
7846
7798
  const INTERACTION_COMMANDS2 = /* @__PURE__ */ new Set([
@@ -7905,15 +7857,6 @@ function createRPCHandler() {
7905
7857
  return handleNetworkFeedback(params);
7906
7858
  case "network:export":
7907
7859
  return handleNetworkExport(params);
7908
- // ── Recording ──
7909
- case "recording:status":
7910
- return handleRecordingStatus(params);
7911
- case "recording:events":
7912
- return handleRecordingEvents(params);
7913
- case "recording:clear":
7914
- return handleRecordingClear(params);
7915
- case "recording:save":
7916
- return handleRecordingSave(params);
7917
7860
  // ── Command log ──
7918
7861
  case "command:log":
7919
7862
  return handleCommandLog(params);
@@ -7980,7 +7923,6 @@ function createRPCHandler() {
7980
7923
  session = await createSession(name, url);
7981
7924
  }
7982
7925
  }
7983
- await injectRecording(session.page);
7984
7926
  if (previewWS) previewWS.registerSession(session.name, session.page);
7985
7927
  saveSessionDiskMeta(name, {
7986
7928
  id: session.id,
@@ -8201,61 +8143,6 @@ function createRPCHandler() {
8201
8143
  if (!entry.capture) return { error: `Entry #${id} not found` };
8202
8144
  return exportEntry(entry.capture, lang);
8203
8145
  }
8204
- async function handleRecordingStatus(params) {
8205
- const sess = findSession(params.session || "default");
8206
- if (!sess) return { recording: false, error: "No session" };
8207
- try {
8208
- const result = await sess.page.evaluate(() => ({
8209
- active: !!window.__xb_rec,
8210
- events: window.__xb_evts?.length || 0,
8211
- url: location.href
8212
- }));
8213
- return { recording: true, ...result };
8214
- } catch {
8215
- return { recording: false, error: "Page unreachable" };
8216
- }
8217
- }
8218
- async function handleRecordingEvents(params) {
8219
- const sess = findSession(params.session || "default");
8220
- if (!sess) return { events: [], error: "No session" };
8221
- try {
8222
- const events = await sess.page.evaluate(() => window.__xb_evts || []);
8223
- return { events, url: sess.page.url() };
8224
- } catch {
8225
- return { events: [], error: "Page unreachable" };
8226
- }
8227
- }
8228
- async function handleRecordingClear(params) {
8229
- const sess = findSession(params.session || "default");
8230
- if (!sess) return { ok: false, error: "No session" };
8231
- try {
8232
- await sess.page.evaluate(() => {
8233
- window.__xb_evts = [];
8234
- window.__xb_t0 = Date.now();
8235
- });
8236
- return { ok: true };
8237
- } catch {
8238
- return { ok: false, error: "Page unreachable" };
8239
- }
8240
- }
8241
- async function handleRecordingSave(params) {
8242
- const sess = findSession(params.session || "default");
8243
- if (!sess) return { ok: false, error: "No session" };
8244
- try {
8245
- const events = await sess.page.evaluate(() => window.__xb_evts || []);
8246
- const recordingsDir = join7(CONFIG_DIR3, "recordings");
8247
- mkdirSync5(recordingsDir, { recursive: true });
8248
- const outPath = params.path || join7(recordingsDir, `recording-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.json`);
8249
- writeFileSync5(outPath, JSON.stringify({
8250
- startUrl: sess.page.url(),
8251
- recordedAt: (/* @__PURE__ */ new Date()).toISOString(),
8252
- events
8253
- }, null, 2));
8254
- return { ok: true, path: outPath, events: events.length };
8255
- } catch (e) {
8256
- return { ok: false, error: errMsg(e) };
8257
- }
8258
- }
8259
8146
  function handleCommandLog(params) {
8260
8147
  const sessionName = params.session || "default";
8261
8148
  const limit = params.limit || 50;
@@ -8293,7 +8180,7 @@ function createRPCHandler() {
8293
8180
  } catch {
8294
8181
  }
8295
8182
  }
8296
- recorder.recordCommandAction({
8183
+ await recorder.recordCommandAction({
8297
8184
  type: actionType,
8298
8185
  selector,
8299
8186
  value,
@@ -8316,7 +8203,6 @@ function createRPCHandler() {
8316
8203
  try {
8317
8204
  const sessionOpts = cdpEndpoint ? { cdpEndpoint } : void 0;
8318
8205
  session = await createSession(sessionName, url, sessionOpts);
8319
- await injectRecording(session.page);
8320
8206
  if (previewWS) previewWS.registerSession(session.name, session.page);
8321
8207
  saveSessionDiskMeta(sessionName, {
8322
8208
  id: session.id,
@@ -8352,8 +8238,8 @@ function createRPCHandler() {
8352
8238
  const existingData = SessionRecorder.readData(sessionName);
8353
8239
  if (existingData) {
8354
8240
  if (outputPath) {
8355
- const { writeFileSync: writeFileSync7 } = await import("fs");
8356
- 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");
8357
8243
  }
8358
8244
  return {
8359
8245
  ok: true,
@@ -8370,14 +8256,14 @@ function createRPCHandler() {
8370
8256
  const { data, summary } = await recorder.stop();
8371
8257
  activeRecorders.delete(sessionName);
8372
8258
  if (outputPath) {
8373
- const { writeFileSync: writeFileSync7, mkdirSync: mkdirSync7 } = await import("fs");
8259
+ const { writeFileSync: writeFileSync6, mkdirSync: mkdirSync6 } = await import("fs");
8374
8260
  const { dirname: dirname2 } = await import("path");
8375
- mkdirSync7(dirname2(outputPath), { recursive: true });
8261
+ mkdirSync6(dirname2(outputPath), { recursive: true });
8376
8262
  if (outputPath.endsWith(".yaml") || outputPath.endsWith(".yml")) {
8377
8263
  const yaml2 = (await import("yaml")).default;
8378
- writeFileSync7(outputPath, yaml2.stringify(data), "utf-8");
8264
+ writeFileSync6(outputPath, yaml2.stringify(data), "utf-8");
8379
8265
  } else {
8380
- writeFileSync7(outputPath, JSON.stringify(data, null, 2), "utf-8");
8266
+ writeFileSync6(outputPath, JSON.stringify(data, null, 2), "utf-8");
8381
8267
  }
8382
8268
  }
8383
8269
  return {
@@ -9586,12 +9472,12 @@ var FileListHandler = class {
9586
9472
  const msg = ctx.message;
9587
9473
  try {
9588
9474
  const { readdirSync, statSync } = await import("fs");
9589
- const { join: join9, resolve } = await import("path");
9475
+ const { join: join8, resolve } = await import("path");
9590
9476
  const targetPath = resolve(msg.path);
9591
9477
  const entries = readdirSync(targetPath);
9592
9478
  const files = entries.map((name) => {
9593
9479
  try {
9594
- const stat = statSync(join9(targetPath, name));
9480
+ const stat = statSync(join8(targetPath, name));
9595
9481
  return { name, isDir: stat.isDirectory(), size: stat.size, modified: stat.mtime.toISOString() };
9596
9482
  } catch {
9597
9483
  return { name, isDir: false, size: 0, modified: "" };
@@ -11252,8 +11138,8 @@ connectWS();
11252
11138
  }
11253
11139
 
11254
11140
  // src/daemon/daemon-main.ts
11255
- var CONFIG_DIR4 = join8(homedir8(), ".xbrowser");
11256
- var LOG_FILE = join8(CONFIG_DIR4, "daemon.log");
11141
+ var CONFIG_DIR3 = join7(homedir7(), ".xbrowser");
11142
+ var LOG_FILE = join7(CONFIG_DIR3, "daemon.log");
11257
11143
  function log(msg) {
11258
11144
  const ts = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").substring(0, 19);
11259
11145
  const line = `[DAEMON ${ts}] ${msg}
@@ -11285,7 +11171,7 @@ async function main() {
11285
11171
  if (err.code === "EADDRINUSE") {
11286
11172
  log(`Port ${daemonPort} already in use \u2014 another daemon instance likely won the startup race. Exiting gracefully.`);
11287
11173
  try {
11288
- unlinkSync(join8(CONFIG_DIR4, "daemon.json"));
11174
+ unlinkSync(join7(CONFIG_DIR3, "daemon.json"));
11289
11175
  } catch {
11290
11176
  }
11291
11177
  process.exit(0);
@@ -11319,8 +11205,8 @@ async function main() {
11319
11205
  rpcHandler.setPreviewWS(previewWS);
11320
11206
  previewWS.on("screencast-started", (sid) => log(`Preview screencast started: ${sid}`));
11321
11207
  previewWS.on("screencast-stopped", (sid) => log(`Preview screencast stopped: ${sid}`));
11322
- mkdirSync6(CONFIG_DIR4, { recursive: true });
11323
- writeFileSync6(join8(CONFIG_DIR4, "daemon.json"), JSON.stringify({
11208
+ mkdirSync5(CONFIG_DIR3, { recursive: true });
11209
+ writeFileSync5(join7(CONFIG_DIR3, "daemon.json"), JSON.stringify({
11324
11210
  port: daemonPort,
11325
11211
  pid: process.pid,
11326
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