@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.
- package/dist/{browser-XPKM344Y.js → browser-4KRHALJ3.js} +1 -1
- package/dist/{browser-LW2MDJE4.js → browser-6WJHQDPV.js} +1 -1
- package/dist/{browser-TLIDFFEG.js → browser-C63PFGPT.js} +1 -1
- package/dist/{chunk-CJJ564VK.js → chunk-MSQIPXKE.js} +1 -1
- package/dist/{chunk-HXLMMSU3.js → chunk-OOJ2H7IL.js} +1 -1
- package/dist/{chunk-FL6DOSWV.js → chunk-TTZNR3QP.js} +30 -2
- package/dist/{chunk-RMYEHTLS.js → chunk-YSCY52UJ.js} +30 -2
- package/dist/{chunk-SJLRCE6N.js → chunk-Z7WC4P2M.js} +1 -1
- package/dist/cli.js +6 -6
- package/dist/daemon-main.js +21 -143
- package/dist/index.d.ts +11 -1
- package/dist/index.js +5 -5
- package/dist/{session-recorder-33UKWCIR.js → session-recorder-KPU4YQAE.js} +1 -1
- package/dist/{session-recorder-BMIJ3ZJM.js → session-recorder-UTULJFTE.js} +1 -1
- package/package.json +1 -1
|
@@ -656,7 +656,7 @@ async function closeSessionByName(name) {
|
|
|
656
656
|
} catch {
|
|
657
657
|
}
|
|
658
658
|
try {
|
|
659
|
-
const { SessionRecorder } = await import("./session-recorder-
|
|
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-
|
|
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
|
-
|
|
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
|
-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
13472
|
+
const { ensureProcessCanExit } = await import("./browser-4KRHALJ3.js");
|
|
13473
13473
|
await ensureProcessCanExit().catch(() => {
|
|
13474
13474
|
});
|
|
13475
13475
|
process.exit(exitCode);
|
package/dist/daemon-main.js
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
} from "./chunk-VEDJ5XSQ.js";
|
|
6
6
|
import {
|
|
7
7
|
SessionRecorder
|
|
8
|
-
} from "./chunk-
|
|
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-
|
|
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
|
|
42
|
-
import { join as
|
|
43
|
-
import { homedir as
|
|
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 {
|
|
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-
|
|
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:
|
|
8364
|
-
|
|
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:
|
|
8259
|
+
const { writeFileSync: writeFileSync6, mkdirSync: mkdirSync6 } = await import("fs");
|
|
8382
8260
|
const { dirname: dirname2 } = await import("path");
|
|
8383
|
-
|
|
8261
|
+
mkdirSync6(dirname2(outputPath), { recursive: true });
|
|
8384
8262
|
if (outputPath.endsWith(".yaml") || outputPath.endsWith(".yml")) {
|
|
8385
8263
|
const yaml2 = (await import("yaml")).default;
|
|
8386
|
-
|
|
8264
|
+
writeFileSync6(outputPath, yaml2.stringify(data), "utf-8");
|
|
8387
8265
|
} else {
|
|
8388
|
-
|
|
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:
|
|
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(
|
|
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
|
|
11264
|
-
var LOG_FILE =
|
|
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(
|
|
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
|
-
|
|
11331
|
-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
13522
|
+
const { setActivePage: setActivePage2 } = await import("./browser-C63PFGPT.js");
|
|
13523
13523
|
setActivePage2(session, targetPage);
|
|
13524
13524
|
}
|
|
13525
13525
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xbrowser/cli",
|
|
3
|
-
"version": "1.
|
|
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": {
|