@xbrowser/cli 1.9.5 → 1.9.7
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-6WJHQDPV.js → browser-ALPLJNH2.js} +1 -1
- package/dist/{browser-4KRHALJ3.js → browser-JXIJF4VL.js} +1 -1
- package/dist/{browser-C63PFGPT.js → browser-ZPQP6VW7.js} +1 -1
- package/dist/{chunk-OOJ2H7IL.js → chunk-37SHZSUR.js} +1 -1
- package/dist/{chunk-MSQIPXKE.js → chunk-5JSOGINQ.js} +1 -1
- package/dist/{chunk-Z7WC4P2M.js → chunk-74S7UUI2.js} +1 -1
- package/dist/{chunk-TTZNR3QP.js → chunk-C4O73DQL.js} +165 -50
- package/dist/{chunk-YSCY52UJ.js → chunk-CH5ZQV64.js} +165 -50
- package/dist/{chunk-53LRNYPG.js → chunk-YAUG3NSI.js} +2 -2
- package/dist/cli.js +82 -18
- package/dist/{daemon-client-PZX2KOLQ.js → daemon-client-4HJENICO.js} +2 -2
- package/dist/{daemon-client-TOUDMIY5.js → daemon-client-CMRAWXTN.js} +1 -1
- package/dist/daemon-main.js +12 -8
- package/dist/index.d.ts +24 -3
- package/dist/index.js +81 -17
- package/dist/{session-recorder-UTULJFTE.js → session-recorder-7GXI42FO.js} +1 -1
- package/dist/{session-recorder-KPU4YQAE.js → session-recorder-RFOPX4SF.js} +1 -1
- package/dist/{session-replayer-RWFOWH3F.js → session-replayer-3HEIJBJ2.js} +65 -33
- package/package.json +1 -1
|
@@ -65,16 +65,28 @@ function getSelectorGeneratorScript() {
|
|
|
65
65
|
if (isUnique(root, s)) return { selector: s, strategy: 'id+tag', confidence: 'high' };
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
68
|
+
// [data-testid]
|
|
69
|
+
for (var si = 0; si < STABLE_ATTRS.length; si++) {
|
|
70
|
+
var v = el.getAttribute(STABLE_ATTRS[si]);
|
|
71
|
+
if (v) {
|
|
72
|
+
var s = '[' + STABLE_ATTRS[si] + '="' + esc(v) + '"]';
|
|
73
|
+
if (isUnique(root, s)) return { selector: s, strategy: 'testid', confidence: 'high' };
|
|
74
|
+
s = tag + s;
|
|
75
|
+
if (isUnique(root, s)) return { selector: s, strategy: 'testid+tag', confidence: 'high' };
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// [data-*] \u2014 other data attributes beyond known testids
|
|
80
|
+
for (var ai = 0; ai < el.attributes.length; ai++) {
|
|
81
|
+
var a = el.attributes[ai];
|
|
82
|
+
if (!a.name.startsWith('data-')) continue;
|
|
83
|
+
if (STABLE_ATTRS.indexOf(a.name) !== -1) continue;
|
|
84
|
+
if (!a.value || a.value.length > 60 || a.value.indexOf('http') === 0) continue;
|
|
85
|
+
var s = '[' + a.name + '="' + esc(a.value) + '"]';
|
|
86
|
+
if (isUnique(root, s)) return { selector: s, strategy: 'data-attr', confidence: 'high' };
|
|
87
|
+
s = tag + s;
|
|
88
|
+
if (isUnique(root, s)) return { selector: s, strategy: 'data-attr+tag', confidence: 'high' };
|
|
89
|
+
}
|
|
78
90
|
|
|
79
91
|
// [name]
|
|
80
92
|
var name = el.getAttribute('name');
|
|
@@ -83,14 +95,35 @@ function getSelectorGeneratorScript() {
|
|
|
83
95
|
if (isUnique(root, s)) return { selector: s, strategy: 'name', confidence: 'high' };
|
|
84
96
|
}
|
|
85
97
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
98
|
+
// [aria-label]
|
|
99
|
+
var aria = el.getAttribute('aria-label');
|
|
100
|
+
if (aria) {
|
|
101
|
+
var s = '[aria-label="' + esc(aria.substring(0, 60)) + '"]';
|
|
102
|
+
if (isUnique(root, s)) return { selector: s, strategy: 'aria-label', confidence: 'high' };
|
|
103
|
+
s = tag + s;
|
|
104
|
+
if (isUnique(root, s)) return { selector: s, strategy: 'aria-label+tag', confidence: 'high' };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// [role][name] combo
|
|
108
|
+
var role = el.getAttribute('role');
|
|
109
|
+
if (role) {
|
|
110
|
+
var name2 = el.getAttribute('name');
|
|
111
|
+
if (name2) {
|
|
112
|
+
var s = '[role="' + role + '"][name="' + esc(name2) + '"]';
|
|
113
|
+
if (isUnique(root, s)) return { selector: s, strategy: 'role+name', confidence: 'high' };
|
|
114
|
+
}
|
|
115
|
+
// [role][aria-label] combo
|
|
116
|
+
if (aria) {
|
|
117
|
+
var s = '[role="' + role + '"][aria-label="' + esc(aria.substring(0, 60)) + '"]';
|
|
118
|
+
if (isUnique(root, s)) return { selector: s, strategy: 'role+aria-label', confidence: 'high' };
|
|
119
|
+
}
|
|
120
|
+
// [role] alone
|
|
121
|
+
var text2 = (el.textContent || '').trim();
|
|
122
|
+
if (text2) {
|
|
123
|
+
var s = '[role="' + role + '"]';
|
|
124
|
+
if (isUnique(root, s)) return { selector: s, strategy: 'role', confidence: 'high' };
|
|
125
|
+
}
|
|
126
|
+
}
|
|
94
127
|
|
|
95
128
|
// [placeholder]
|
|
96
129
|
var ph = el.getAttribute('placeholder');
|
|
@@ -113,11 +146,12 @@ function getSelectorGeneratorScript() {
|
|
|
113
146
|
if (isUnique(root, s)) return { selector: s, strategy: 'title', confidence: 'high' };
|
|
114
147
|
}
|
|
115
148
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
149
|
+
// Unique attribute (skip URL-like, long, and common attrs handled above)
|
|
150
|
+
// Note: non-testid data-* attrs are already tried in [data-*] section above
|
|
151
|
+
var skipAttr = {class:1,style:1,id:1,name:1,'aria-label':1,placeholder:1,alt:1,title:1,role:1,src:1,href:1,action:1,'data-src':1,'data-href':1};
|
|
152
|
+
for (var ai = 0; ai < el.attributes.length; ai++) {
|
|
153
|
+
var a = el.attributes[ai];
|
|
154
|
+
if (skipAttr[a.name] || a.name.startsWith('data-') || a.name.startsWith('aria-')) continue;
|
|
121
155
|
if (a.value && a.value.length > 2 && a.value.length <= 60) {
|
|
122
156
|
var s = tag + '[' + a.name + '="' + esc(a.value) + '"]';
|
|
123
157
|
if (isUnique(root, s)) return { selector: s, strategy: 'attribute', confidence: 'medium' };
|
|
@@ -154,10 +188,16 @@ function getSelectorGeneratorScript() {
|
|
|
154
188
|
var s = tag + '.' + esc(cls[i]) + '.' + esc(cls[j]);
|
|
155
189
|
if (isUnique(root, s)) return { selector: s, strategy: 'tag+class-combo', confidence: 'medium' };
|
|
156
190
|
}
|
|
157
|
-
|
|
158
|
-
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// tag[aria-label] fallback \u2014 aria-label exists but wasn't globally unique
|
|
195
|
+
if (aria) {
|
|
196
|
+
var s = tag + '[aria-label="' + esc(aria.substring(0, 60)) + '"]';
|
|
197
|
+
if (isUnique(root, s)) return { selector: s, strategy: 'tag+aria-label', confidence: 'medium' };
|
|
198
|
+
}
|
|
159
199
|
|
|
160
|
-
|
|
200
|
+
// Parent scope
|
|
161
201
|
var parent = el.parentElement;
|
|
162
202
|
if (parent && parent !== root) {
|
|
163
203
|
if (parent.id) {
|
|
@@ -1224,12 +1264,28 @@ var SessionRecorder = class _SessionRecorder {
|
|
|
1224
1264
|
activePages = /* @__PURE__ */ new Set();
|
|
1225
1265
|
lastKnownUrl = "";
|
|
1226
1266
|
// Track URL to detect real navigation changes
|
|
1227
|
-
/** Dedup
|
|
1228
|
-
cdpActionDedup
|
|
1267
|
+
/** Dedup map: key = normalizedType|tag|value → expiration timestamp.
|
|
1268
|
+
* Replaces old single-entry cdpActionDedup for bidirectional dedup. */
|
|
1269
|
+
dedupMap = /* @__PURE__ */ new Map();
|
|
1270
|
+
dedupActionCount = 0;
|
|
1271
|
+
// counter for periodic cleanup
|
|
1229
1272
|
/** Network dedup: last request key for short-window dedup */
|
|
1230
1273
|
_lastRequestKey = "";
|
|
1231
1274
|
_lastRequestTs = 0;
|
|
1232
1275
|
_isRecording = false;
|
|
1276
|
+
streamMode = "clean";
|
|
1277
|
+
/** Ambient action types: filtered out in clean mode */
|
|
1278
|
+
static AMBIENT_TYPES = /* @__PURE__ */ new Set([
|
|
1279
|
+
"hover",
|
|
1280
|
+
"focus",
|
|
1281
|
+
"scroll",
|
|
1282
|
+
"visibility",
|
|
1283
|
+
"resize",
|
|
1284
|
+
"clipboard",
|
|
1285
|
+
"touch",
|
|
1286
|
+
"contextmenu",
|
|
1287
|
+
"drag"
|
|
1288
|
+
]);
|
|
1233
1289
|
constructor(context, page, sessionName) {
|
|
1234
1290
|
this.context = context;
|
|
1235
1291
|
this.page = page;
|
|
@@ -1243,13 +1299,10 @@ var SessionRecorder = class _SessionRecorder {
|
|
|
1243
1299
|
}
|
|
1244
1300
|
/** Record an action triggered by a CDP command (e.g. xbrowser fill/click/goto) */
|
|
1245
1301
|
async recordCommandAction(action) {
|
|
1246
|
-
const
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
const valueMatch = !action.value || recent.value === action.value;
|
|
1251
|
-
const selectorMatch = !action.selector || recent.element?.selector && (recent.element.selector === action.selector || recent.element.selector.endsWith(" " + action.selector) || action.selector.endsWith(" " + recent.element.selector));
|
|
1252
|
-
if (typeMatch && valueMatch && selectorMatch) {
|
|
1302
|
+
const dedupFromAction = this.dedupKey(action.type, action.selector, action.element?.tag, action.value);
|
|
1303
|
+
if (dedupFromAction) {
|
|
1304
|
+
const expires = this.dedupMap.get(dedupFromAction);
|
|
1305
|
+
if (expires && Date.now() < expires) {
|
|
1253
1306
|
return;
|
|
1254
1307
|
}
|
|
1255
1308
|
}
|
|
@@ -1271,18 +1324,26 @@ var SessionRecorder = class _SessionRecorder {
|
|
|
1271
1324
|
element: actionToPush.element
|
|
1272
1325
|
});
|
|
1273
1326
|
this.lastActionTs = ts;
|
|
1274
|
-
this.
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1327
|
+
this.dedupMap.set(dedupFromAction, Date.now() + 2e3);
|
|
1328
|
+
this.dedupActionCount++;
|
|
1329
|
+
if (this.dedupActionCount % 200 === 0) {
|
|
1330
|
+
const now = Date.now();
|
|
1331
|
+
for (const [k, v] of this.dedupMap) {
|
|
1332
|
+
if (now >= v) this.dedupMap.delete(k);
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1280
1335
|
if (action.url && action.url !== "about:blank") {
|
|
1281
1336
|
this.lastKnownUrl = action.url;
|
|
1282
1337
|
} else if (action.type === "goto" && action.value && action.value !== "about:blank") {
|
|
1283
1338
|
this.lastKnownUrl = action.value;
|
|
1284
1339
|
}
|
|
1285
1340
|
}
|
|
1341
|
+
/** Generate dedup key for an action (normalized type + selector/tag + value). */
|
|
1342
|
+
dedupKey(type, selector, tag, value) {
|
|
1343
|
+
const normType = type === "cdp-click" ? "click" : type === "cdp-fill" ? "input" : type === "cdp-eval" ? "eval" : type;
|
|
1344
|
+
const selOrTag = selector || tag || "";
|
|
1345
|
+
return `${normType}|${selOrTag}|${value || ""}`;
|
|
1346
|
+
}
|
|
1286
1347
|
get networkCount() {
|
|
1287
1348
|
return this.network.length;
|
|
1288
1349
|
}
|
|
@@ -1320,7 +1381,7 @@ var SessionRecorder = class _SessionRecorder {
|
|
|
1320
1381
|
return join(this.recordingsDir, ".stop");
|
|
1321
1382
|
}
|
|
1322
1383
|
// ─── Start ──────────────────────────────────────────────────────
|
|
1323
|
-
async start(url) {
|
|
1384
|
+
async start(url, options) {
|
|
1324
1385
|
if (this._isRecording) throw new Error("Already recording");
|
|
1325
1386
|
this._isRecording = true;
|
|
1326
1387
|
this.startedAt = Date.now();
|
|
@@ -1329,6 +1390,7 @@ var SessionRecorder = class _SessionRecorder {
|
|
|
1329
1390
|
this.contextChanges = [];
|
|
1330
1391
|
this.checkpoints = [];
|
|
1331
1392
|
this.checkpointCounter = 0;
|
|
1393
|
+
this.streamMode = options?.stream ?? "clean";
|
|
1332
1394
|
this.lastKnownUrl = this.page.url();
|
|
1333
1395
|
await this.page.addInitScript(getSelectorGeneratorScript());
|
|
1334
1396
|
await this.page.addInitScript(ACTION_SIGNAL_SCRIPT);
|
|
@@ -1897,15 +1959,16 @@ var SessionRecorder = class _SessionRecorder {
|
|
|
1897
1959
|
}
|
|
1898
1960
|
for (const raw of pending) {
|
|
1899
1961
|
if (raw.ts <= this.lastActionTs) continue;
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
const
|
|
1903
|
-
|
|
1904
|
-
const selectorMatch = !dedup.selector || raw.element?.selector && (raw.element.selector === dedup.selector || raw.element.selector.endsWith(" " + dedup.selector) || dedup.selector.endsWith(" " + raw.element.selector));
|
|
1905
|
-
if (typeMatch && valueMatch && selectorMatch) {
|
|
1962
|
+
const dedupFromSignal = this.dedupKey(raw.type, raw.element?.selector, raw.element?.tag, raw.value);
|
|
1963
|
+
if (dedupFromSignal) {
|
|
1964
|
+
const expires = this.dedupMap.get(dedupFromSignal);
|
|
1965
|
+
if (expires && Date.now() < expires) {
|
|
1906
1966
|
continue;
|
|
1907
1967
|
}
|
|
1908
1968
|
}
|
|
1969
|
+
if (this.streamMode === "clean" && _SessionRecorder.AMBIENT_TYPES.has(raw.type)) {
|
|
1970
|
+
continue;
|
|
1971
|
+
}
|
|
1909
1972
|
this.actionCounter++;
|
|
1910
1973
|
let clickContext;
|
|
1911
1974
|
if (raw.type === "click" && raw.x !== void 0 && raw.y !== void 0) {
|
|
@@ -1930,6 +1993,18 @@ var SessionRecorder = class _SessionRecorder {
|
|
|
1930
1993
|
const pushed = this.actions[this.actions.length - 1];
|
|
1931
1994
|
pushed.elementScreenshot = await this.captureElementScreenshot(page, raw);
|
|
1932
1995
|
this.lastActionTs = raw.ts;
|
|
1996
|
+
const dedupKey = this.dedupKey(raw.type, raw.element?.selector, raw.element?.tag, raw.value);
|
|
1997
|
+
if (dedupKey) {
|
|
1998
|
+
this.dedupMap.set(dedupKey, Date.now() + 2e3);
|
|
1999
|
+
this.dedupActionCount++;
|
|
2000
|
+
}
|
|
2001
|
+
const verifyTypes = /* @__PURE__ */ new Set(["click", "submit", "input", "change", "cdp-click", "cdp-fill"]);
|
|
2002
|
+
if (verifyTypes.has(raw.type)) {
|
|
2003
|
+
const signals = await this.verifyAction(page, pushed).catch(() => []);
|
|
2004
|
+
if (signals.length > 0) {
|
|
2005
|
+
pushed.__signals = signals;
|
|
2006
|
+
}
|
|
2007
|
+
}
|
|
1933
2008
|
if (raw.type === "click" || raw.type === "navigate" || raw.type === "submit") {
|
|
1934
2009
|
const detected = await this.detectCheckpoints(page);
|
|
1935
2010
|
for (const cp of detected) {
|
|
@@ -2112,6 +2187,44 @@ var SessionRecorder = class _SessionRecorder {
|
|
|
2112
2187
|
return [];
|
|
2113
2188
|
}
|
|
2114
2189
|
}
|
|
2190
|
+
// ─── Verification: collect success signals after key actions ──────
|
|
2191
|
+
/**
|
|
2192
|
+
* After a key action (click/submit/input), check for success signals:
|
|
2193
|
+
* URL changes, recent successful network responses, dialog appearances.
|
|
2194
|
+
*/
|
|
2195
|
+
async verifyAction(page, action) {
|
|
2196
|
+
const signals = [];
|
|
2197
|
+
try {
|
|
2198
|
+
const currentUrl = page.url();
|
|
2199
|
+
if (currentUrl && currentUrl !== "about:blank" && currentUrl !== action.url) {
|
|
2200
|
+
signals.push({ type: "url_change", value: currentUrl, label: `URL changed to ${currentUrl}` });
|
|
2201
|
+
}
|
|
2202
|
+
} catch {
|
|
2203
|
+
}
|
|
2204
|
+
try {
|
|
2205
|
+
const recentNetwork = this.network.filter(
|
|
2206
|
+
(n) => n.timestamp > action.timestamp - 100 && n.timestamp < action.timestamp + 5e3 && n.status >= 200 && n.status < 400
|
|
2207
|
+
);
|
|
2208
|
+
for (const net of recentNetwork.slice(0, 3)) {
|
|
2209
|
+
signals.push({
|
|
2210
|
+
type: "network_success",
|
|
2211
|
+
value: `${net.status}`,
|
|
2212
|
+
label: `${net.method} ${net.path} \u2192 ${net.status}`
|
|
2213
|
+
});
|
|
2214
|
+
}
|
|
2215
|
+
} catch {
|
|
2216
|
+
}
|
|
2217
|
+
try {
|
|
2218
|
+
const recentDialog = [...this.checkpoints].reverse().find(
|
|
2219
|
+
(cp) => cp.type === "dialog" && Math.abs(cp.timestamp - action.timestamp) < 3e3
|
|
2220
|
+
);
|
|
2221
|
+
if (recentDialog) {
|
|
2222
|
+
signals.push({ type: "dialog", value: recentDialog.type, label: recentDialog.hint });
|
|
2223
|
+
}
|
|
2224
|
+
} catch {
|
|
2225
|
+
}
|
|
2226
|
+
return signals;
|
|
2227
|
+
}
|
|
2115
2228
|
// ─── Periodic disk flush ────────────────────────────────────────
|
|
2116
2229
|
flushToDisk() {
|
|
2117
2230
|
const data = this.buildData();
|
|
@@ -2182,7 +2295,7 @@ var SessionRecorder = class _SessionRecorder {
|
|
|
2182
2295
|
return false;
|
|
2183
2296
|
};
|
|
2184
2297
|
const meaningfulNetwork = data.network.filter((n) => !isNoiseNetwork(n));
|
|
2185
|
-
const filtered = data.actions
|
|
2298
|
+
const filtered = data.actions;
|
|
2186
2299
|
const groups = [];
|
|
2187
2300
|
let current = null;
|
|
2188
2301
|
for (const action of filtered) {
|
|
@@ -2209,6 +2322,7 @@ var SessionRecorder = class _SessionRecorder {
|
|
|
2209
2322
|
);
|
|
2210
2323
|
const matchedInputs = inputAction ? this.matchActionToNetwork(inputAction, nearbyNetwork) : [];
|
|
2211
2324
|
const clickMatches = primary.type === "click" && primary.element?.text ? this.matchActionToNetwork(primary, nearbyNetwork) : [];
|
|
2325
|
+
const signals = primary.__signals || [];
|
|
2212
2326
|
steps.push({
|
|
2213
2327
|
step: steps.length + 1,
|
|
2214
2328
|
ref: getRef(primary),
|
|
@@ -2218,7 +2332,8 @@ var SessionRecorder = class _SessionRecorder {
|
|
|
2218
2332
|
responseBody: n.responseBody && JSON.stringify(n.responseBody).length > 1e3 ? "[truncated, " + JSON.stringify(n.responseBody).length + " bytes]" : n.responseBody
|
|
2219
2333
|
})),
|
|
2220
2334
|
contextChanges: nearbyContext,
|
|
2221
|
-
matchedInputs: [...matchedInputs, ...clickMatches]
|
|
2335
|
+
matchedInputs: [...matchedInputs, ...clickMatches],
|
|
2336
|
+
signals
|
|
2222
2337
|
});
|
|
2223
2338
|
}
|
|
2224
2339
|
return {
|
|
@@ -300,8 +300,8 @@ async function forwardNetworkExport(sessionName, id, lang) {
|
|
|
300
300
|
async function forwardNetworkInspect(sessionName, id) {
|
|
301
301
|
return rpcCall("network:inspect", { session: sessionName, id }, 1e4);
|
|
302
302
|
}
|
|
303
|
-
async function forwardRecordStart(session, url, cdpEndpoint) {
|
|
304
|
-
return rpcCall("record:start", { session, url, cdpEndpoint }, 15e3);
|
|
303
|
+
async function forwardRecordStart(session, url, cdpEndpoint, stream) {
|
|
304
|
+
return rpcCall("record:start", { session, url, cdpEndpoint, stream }, 15e3);
|
|
305
305
|
}
|
|
306
306
|
async function forwardRecordStop(session, output) {
|
|
307
307
|
const params = { session };
|
package/dist/cli.js
CHANGED
|
@@ -27,10 +27,10 @@ import {
|
|
|
27
27
|
startDaemonProcess,
|
|
28
28
|
stopDaemonProcess,
|
|
29
29
|
version
|
|
30
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-YAUG3NSI.js";
|
|
31
31
|
import {
|
|
32
32
|
SessionRecorder
|
|
33
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-CH5ZQV64.js";
|
|
34
34
|
import {
|
|
35
35
|
addKnownIssue,
|
|
36
36
|
getKnowledgePath,
|
|
@@ -54,7 +54,7 @@ import {
|
|
|
54
54
|
resolveLaunchOpts,
|
|
55
55
|
saveSessionDiskMeta,
|
|
56
56
|
setActivePage
|
|
57
|
-
} from "./chunk-
|
|
57
|
+
} from "./chunk-74S7UUI2.js";
|
|
58
58
|
import "./chunk-TNEN6VQ2.js";
|
|
59
59
|
import {
|
|
60
60
|
errMsg
|
|
@@ -7058,7 +7058,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
7058
7058
|
}
|
|
7059
7059
|
let targetPageOverride = null;
|
|
7060
7060
|
if (_target && extraOpts?.cdpEndpoint) {
|
|
7061
|
-
const { findTargetPage } = await import("./browser-
|
|
7061
|
+
const { findTargetPage } = await import("./browser-JXIJF4VL.js");
|
|
7062
7062
|
targetPageOverride = await findTargetPage(extraOpts.cdpEndpoint, _target);
|
|
7063
7063
|
if (!targetPageOverride) {
|
|
7064
7064
|
return errorResult(`Target "${_target}" not found. Use 'xbrowser targets --cdp ${extraOpts.cdpEndpoint}' to list available pages.`);
|
|
@@ -7075,7 +7075,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
7075
7075
|
params = result.data;
|
|
7076
7076
|
}
|
|
7077
7077
|
if (command.scope !== "cli" && !process.env.XBROWSER_DAEMON_WORKER) {
|
|
7078
|
-
const { forwardExec } = await import("./daemon-client-
|
|
7078
|
+
const { forwardExec } = await import("./daemon-client-CMRAWXTN.js");
|
|
7079
7079
|
const result = await forwardExec(commandName, params, sessionName, extraOpts?.cdpEndpoint);
|
|
7080
7080
|
if (result) return result;
|
|
7081
7081
|
}
|
|
@@ -8487,7 +8487,7 @@ var PluginInstaller = class {
|
|
|
8487
8487
|
import { outputFormatter, OutputFormatter } from "@dyyz1993/xcli-core";
|
|
8488
8488
|
var formatter = new OutputFormatter();
|
|
8489
8489
|
function outputResult(result, mode = "text") {
|
|
8490
|
-
if (typeof result === "object" && result !== null) {
|
|
8490
|
+
if (mode !== "json" && mode !== "yaml" && typeof result === "object" && result !== null) {
|
|
8491
8491
|
const r = result;
|
|
8492
8492
|
if (r.success === false) {
|
|
8493
8493
|
outputError(r.message ? String(r.message) : "Unknown error");
|
|
@@ -10325,6 +10325,50 @@ async function infoFromMarketplacePlugin(slug, loader) {
|
|
|
10325
10325
|
}
|
|
10326
10326
|
return null;
|
|
10327
10327
|
}
|
|
10328
|
+
function normalizePluginName(name) {
|
|
10329
|
+
return name.replace(/^@[^/]+\//, "").replace(/^xbrowser-plugin-/, "").toLowerCase();
|
|
10330
|
+
}
|
|
10331
|
+
async function searchLocalPlugins(query) {
|
|
10332
|
+
let installed;
|
|
10333
|
+
try {
|
|
10334
|
+
installed = await new PluginInstaller().list();
|
|
10335
|
+
} catch {
|
|
10336
|
+
return [];
|
|
10337
|
+
}
|
|
10338
|
+
if (!installed || installed.length === 0) return [];
|
|
10339
|
+
const q = normalizePluginName(query);
|
|
10340
|
+
if (!q) return [];
|
|
10341
|
+
const results = [];
|
|
10342
|
+
for (const p of installed) {
|
|
10343
|
+
const plugin = p;
|
|
10344
|
+
const name = String(plugin.name ?? "");
|
|
10345
|
+
const norm = normalizePluginName(name);
|
|
10346
|
+
if (norm === q) {
|
|
10347
|
+
results.push({ plugin, rank: 0 });
|
|
10348
|
+
} else if (norm.startsWith(q) || q.startsWith(norm)) {
|
|
10349
|
+
results.push({ plugin, rank: 1 });
|
|
10350
|
+
} else if (norm.includes(q) || q.includes(norm)) {
|
|
10351
|
+
results.push({ plugin, rank: 2 });
|
|
10352
|
+
}
|
|
10353
|
+
}
|
|
10354
|
+
results.sort((a, b) => a.rank - b.rank);
|
|
10355
|
+
const runtimeInfo = await buildRuntimePluginInfo().catch(() => /* @__PURE__ */ new Map());
|
|
10356
|
+
return results.map(({ plugin }) => {
|
|
10357
|
+
const name = String(plugin.name ?? "");
|
|
10358
|
+
const metadata = plugin.metadata;
|
|
10359
|
+
const staticCommands = metadata?.commands;
|
|
10360
|
+
const rt = runtimeInfo.get(name);
|
|
10361
|
+
const commands = rt?.commands || staticCommands;
|
|
10362
|
+
return {
|
|
10363
|
+
name: "@xbrowser/" + name,
|
|
10364
|
+
slug: name,
|
|
10365
|
+
description: metadata?.description,
|
|
10366
|
+
version: metadata?.version,
|
|
10367
|
+
commands,
|
|
10368
|
+
source: "local"
|
|
10369
|
+
};
|
|
10370
|
+
});
|
|
10371
|
+
}
|
|
10328
10372
|
async function handleSearch(args, options, mode) {
|
|
10329
10373
|
const query = args[0] || "";
|
|
10330
10374
|
applyRegistryOverride(options);
|
|
@@ -10332,6 +10376,8 @@ async function handleSearch(args, options, mode) {
|
|
|
10332
10376
|
const searchLimit = options.limit ? Number(options.limit) : 20;
|
|
10333
10377
|
const searchOpts = { query, tag: options.tag, site: options.site, limit: searchLimit };
|
|
10334
10378
|
const results = [];
|
|
10379
|
+
const localResults = await searchLocalPlugins(query);
|
|
10380
|
+
results.push(...localResults);
|
|
10335
10381
|
const loader = await getPluginLoader();
|
|
10336
10382
|
const pluginResults = await searchFromMarketplacePlugin2(searchOpts, loader);
|
|
10337
10383
|
results.push(...pluginResults);
|
|
@@ -10344,15 +10390,24 @@ async function handleSearch(args, options, mode) {
|
|
|
10344
10390
|
} catch {
|
|
10345
10391
|
}
|
|
10346
10392
|
}
|
|
10393
|
+
const seen = /* @__PURE__ */ new Set();
|
|
10394
|
+
const deduped = [];
|
|
10395
|
+
for (const r of results) {
|
|
10396
|
+
const key = normalizePluginName(String(r.name ?? ""));
|
|
10397
|
+
if (key && !seen.has(key)) {
|
|
10398
|
+
seen.add(key);
|
|
10399
|
+
deduped.push(r);
|
|
10400
|
+
}
|
|
10401
|
+
}
|
|
10347
10402
|
if (mode === "json") {
|
|
10348
|
-
outputEnvelope({ success: true, data: { results, total:
|
|
10403
|
+
outputEnvelope({ success: true, data: { results: deduped, total: deduped.length } }, { command: "plugin search" }, mode);
|
|
10349
10404
|
} else {
|
|
10350
|
-
if (
|
|
10405
|
+
if (deduped.length === 0) {
|
|
10351
10406
|
console.log("No plugins found");
|
|
10352
10407
|
return;
|
|
10353
10408
|
}
|
|
10354
|
-
for (const r of
|
|
10355
|
-
const src = r.source === "marketplace" ? "[marketplace]" : "[npm]";
|
|
10409
|
+
for (const r of deduped) {
|
|
10410
|
+
const src = r.source === "marketplace" ? "[marketplace]" : r.source === "local" ? "[local]" : "[npm]";
|
|
10356
10411
|
const slug = r.slug ? ` (${r.slug})` : "";
|
|
10357
10412
|
console.log(` ${src} ${r.name}${slug}`);
|
|
10358
10413
|
if (r.description) console.log(` ${r.description}`);
|
|
@@ -10360,7 +10415,7 @@ async function handleSearch(args, options, mode) {
|
|
|
10360
10415
|
if (r.downloads) console.log(` Downloads: ${r.downloads}`);
|
|
10361
10416
|
console.log("");
|
|
10362
10417
|
}
|
|
10363
|
-
console.log(`Total: ${
|
|
10418
|
+
console.log(`Total: ${deduped.length} plugins`);
|
|
10364
10419
|
}
|
|
10365
10420
|
}
|
|
10366
10421
|
async function handlePluginInfo(args, options, mode) {
|
|
@@ -10496,7 +10551,7 @@ async function handlePlugin(args, options, mode) {
|
|
|
10496
10551
|
} catch {
|
|
10497
10552
|
}
|
|
10498
10553
|
try {
|
|
10499
|
-
const { daemonPing } = await import("./daemon-client-
|
|
10554
|
+
const { daemonPing } = await import("./daemon-client-CMRAWXTN.js");
|
|
10500
10555
|
if (await daemonPing()) {
|
|
10501
10556
|
await fetch("http://localhost:9224/rpc", {
|
|
10502
10557
|
method: "POST",
|
|
@@ -10654,7 +10709,8 @@ async function handleRecord(args, options, mode) {
|
|
|
10654
10709
|
const url = options.url;
|
|
10655
10710
|
const sessionName = options.session || "default";
|
|
10656
10711
|
const cdpEndpoint = options.cdp;
|
|
10657
|
-
const
|
|
10712
|
+
const stream = options.stream || "clean";
|
|
10713
|
+
const result = await forwardRecordStart(sessionName, url, cdpEndpoint, stream);
|
|
10658
10714
|
if (!result.ok) {
|
|
10659
10715
|
outputError(String(result.error || "Failed to start recording"));
|
|
10660
10716
|
return;
|
|
@@ -10949,7 +11005,15 @@ async function handleReplay(args, options, mode) {
|
|
|
10949
11005
|
const absPath = await import("path").then((p) => p.resolve(filePath));
|
|
10950
11006
|
const result = await forwardReplay(absPath, sessionName, slowMo);
|
|
10951
11007
|
if (!result.ok) {
|
|
10952
|
-
|
|
11008
|
+
if (mode === "json" || mode === "yaml") {
|
|
11009
|
+
outputResult({
|
|
11010
|
+
...result,
|
|
11011
|
+
success: false,
|
|
11012
|
+
message: result.errors ? result.errors.map((e) => e.error).join("; ") : result.error || "Replay failed"
|
|
11013
|
+
}, mode);
|
|
11014
|
+
} else {
|
|
11015
|
+
outputError(String(result.errors ? result.errors.map((e) => e.error).join("; ") : result.error || "Replay failed"));
|
|
11016
|
+
}
|
|
10953
11017
|
return;
|
|
10954
11018
|
}
|
|
10955
11019
|
outputResult(result, mode);
|
|
@@ -11053,7 +11117,7 @@ async function handleFilter(args, _mode, options) {
|
|
|
11053
11117
|
}
|
|
11054
11118
|
}
|
|
11055
11119
|
async function handleGeneratePlugin(sessionName, pluginName, outputDir) {
|
|
11056
|
-
const { SessionRecorder: SessionRecorder2 } = await import("./session-recorder-
|
|
11120
|
+
const { SessionRecorder: SessionRecorder2 } = await import("./session-recorder-RFOPX4SF.js");
|
|
11057
11121
|
const { readSiteKnowledge: readSiteKnowledge2, toMarkdown } = await import("./site-knowledge-SYC6VCDB.js");
|
|
11058
11122
|
const { mkdirSync: mkdirSync10, writeFileSync: writeFileSync12 } = await import("fs");
|
|
11059
11123
|
const { join: join13 } = await import("path");
|
|
@@ -12990,7 +13054,7 @@ Run "xbrowser ${command} ${subCommand} --help" to see available parameters.`
|
|
|
12990
13054
|
}
|
|
12991
13055
|
const needsBrowser = cmdEntry.scope === "page" || cmdEntry.scope === "browser";
|
|
12992
13056
|
if (needsBrowser && !process.env.XBROWSER_DAEMON_WORKER) {
|
|
12993
|
-
const { forwardExec } = await import("./daemon-client-
|
|
13057
|
+
const { forwardExec } = await import("./daemon-client-CMRAWXTN.js");
|
|
12994
13058
|
const userTimeout = typeof params.timeout === "number" && params.timeout > 0 ? params.timeout * 1e3 + 3e4 : void 0;
|
|
12995
13059
|
const result = await forwardExec(`${command}.${subCommand}`, params, sessionName, cdpEndpoint, userTimeout);
|
|
12996
13060
|
const resultData = result && typeof result === "object" && "data" in result ? result.data : void 0;
|
|
@@ -13030,7 +13094,7 @@ Run "xbrowser ${command} ${subCommand} --help" to see available parameters.`
|
|
|
13030
13094
|
const targetPage = pages[cmdTabIndex];
|
|
13031
13095
|
await targetPage.bringToFront().catch(() => {
|
|
13032
13096
|
});
|
|
13033
|
-
const { setActivePage: setActivePage2 } = await import("./browser-
|
|
13097
|
+
const { setActivePage: setActivePage2 } = await import("./browser-JXIJF4VL.js");
|
|
13034
13098
|
setActivePage2(session, targetPage);
|
|
13035
13099
|
}
|
|
13036
13100
|
}
|
|
@@ -13306,7 +13370,7 @@ async function main() {
|
|
|
13306
13370
|
const command = process.argv[2];
|
|
13307
13371
|
const isLongRunning = command === "preview" || command === "serve";
|
|
13308
13372
|
if (!isLongRunning) {
|
|
13309
|
-
const { ensureProcessCanExit } = await import("./browser-
|
|
13373
|
+
const { ensureProcessCanExit } = await import("./browser-JXIJF4VL.js");
|
|
13310
13374
|
await ensureProcessCanExit().catch(() => {
|
|
13311
13375
|
});
|
|
13312
13376
|
process.exit(process.exitCode || exitCode);
|
|
@@ -179,8 +179,8 @@ async function forwardNetworkExport(sessionName, id, lang) {
|
|
|
179
179
|
async function forwardNetworkInspect(sessionName, id) {
|
|
180
180
|
return rpcCall("network:inspect", { session: sessionName, id }, 1e4);
|
|
181
181
|
}
|
|
182
|
-
async function forwardRecordStart(session, url, cdpEndpoint) {
|
|
183
|
-
return rpcCall("record:start", { session, url, cdpEndpoint }, 15e3);
|
|
182
|
+
async function forwardRecordStart(session, url, cdpEndpoint, stream) {
|
|
183
|
+
return rpcCall("record:start", { session, url, cdpEndpoint, stream }, 15e3);
|
|
184
184
|
}
|
|
185
185
|
async function forwardRecordStop(session, output) {
|
|
186
186
|
const params = { session };
|
package/dist/daemon-main.js
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
} from "./chunk-VEDJ5XSQ.js";
|
|
14
14
|
import {
|
|
15
15
|
SessionRecorder
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-C4O73DQL.js";
|
|
17
17
|
import {
|
|
18
18
|
closeEphemeralContext,
|
|
19
19
|
closeSessionByName,
|
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
resolveLaunchOpts,
|
|
30
30
|
saveSessionDiskMeta,
|
|
31
31
|
setActivePage
|
|
32
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-37SHZSUR.js";
|
|
33
33
|
import "./chunk-VJNMAWPZ.js";
|
|
34
34
|
import "./chunk-TNEN6VQ2.js";
|
|
35
35
|
import {
|
|
@@ -6575,7 +6575,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
6575
6575
|
}
|
|
6576
6576
|
let targetPageOverride = null;
|
|
6577
6577
|
if (_target && extraOpts?.cdpEndpoint) {
|
|
6578
|
-
const { findTargetPage } = await import("./browser-
|
|
6578
|
+
const { findTargetPage } = await import("./browser-ALPLJNH2.js");
|
|
6579
6579
|
targetPageOverride = await findTargetPage(extraOpts.cdpEndpoint, _target);
|
|
6580
6580
|
if (!targetPageOverride) {
|
|
6581
6581
|
return errorResult(`Target "${_target}" not found. Use 'xbrowser targets --cdp ${extraOpts.cdpEndpoint}' to list available pages.`);
|
|
@@ -6592,7 +6592,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
6592
6592
|
params = result.data;
|
|
6593
6593
|
}
|
|
6594
6594
|
if (command.scope !== "cli" && !process.env.XBROWSER_DAEMON_WORKER) {
|
|
6595
|
-
const { forwardExec } = await import("./daemon-client-
|
|
6595
|
+
const { forwardExec } = await import("./daemon-client-4HJENICO.js");
|
|
6596
6596
|
const result = await forwardExec(commandName, params, sessionName, extraOpts?.cdpEndpoint);
|
|
6597
6597
|
if (result) return result;
|
|
6598
6598
|
}
|
|
@@ -8013,6 +8013,7 @@ function createRPCHandler() {
|
|
|
8013
8013
|
const sessionName = params.session || "default";
|
|
8014
8014
|
const url = params.url;
|
|
8015
8015
|
const cdpEndpoint = params.cdpEndpoint;
|
|
8016
|
+
const stream = params.stream || "clean";
|
|
8016
8017
|
if (activeRecorders.has(sessionName)) {
|
|
8017
8018
|
return { ok: false, error: "Recording already in progress for session: " + sessionName };
|
|
8018
8019
|
}
|
|
@@ -8044,7 +8045,7 @@ function createRPCHandler() {
|
|
|
8044
8045
|
}
|
|
8045
8046
|
try {
|
|
8046
8047
|
const recorder = new SessionRecorder(session.context, session.page, sessionName);
|
|
8047
|
-
await recorder.start(url);
|
|
8048
|
+
await recorder.start(url, { stream });
|
|
8048
8049
|
activeRecorders.set(sessionName, recorder);
|
|
8049
8050
|
return { ok: true, session: sessionName, startUrl: url || session.page.url() };
|
|
8050
8051
|
} catch (e) {
|
|
@@ -8195,7 +8196,8 @@ function createRPCHandler() {
|
|
|
8195
8196
|
const isNewFormat = Array.isArray(parsed.actions);
|
|
8196
8197
|
if (isNewFormat) {
|
|
8197
8198
|
try {
|
|
8198
|
-
const
|
|
8199
|
+
const replayErrors = [];
|
|
8200
|
+
const { SessionReplayer } = await import("./session-replayer-3HEIJBJ2.js");
|
|
8199
8201
|
const replayer = new SessionReplayer({
|
|
8200
8202
|
page: session.page,
|
|
8201
8203
|
stepDelay: slowMo * 500,
|
|
@@ -8203,7 +8205,9 @@ function createRPCHandler() {
|
|
|
8203
8205
|
console.log(`[replay] Step ${index + 1}/${total}: ${action.type} ${action.element?.selector || action.url || ""}`);
|
|
8204
8206
|
},
|
|
8205
8207
|
onError: (action, error) => {
|
|
8206
|
-
|
|
8208
|
+
const msg = `[${action?.type || "unknown"}] ${error?.message || String(error)}`;
|
|
8209
|
+
console.error("[replay] Error at step:", msg);
|
|
8210
|
+
replayErrors.push({ eventIndex: -1, error: msg });
|
|
8207
8211
|
}
|
|
8208
8212
|
});
|
|
8209
8213
|
if (!Array.isArray(parsed.actions) || typeof parsed.startUrl !== "string") {
|
|
@@ -8219,7 +8223,7 @@ function createRPCHandler() {
|
|
|
8219
8223
|
duration,
|
|
8220
8224
|
eventsPlayed: result2.success,
|
|
8221
8225
|
totalEvents: result2.success + result2.failed + result2.skipped,
|
|
8222
|
-
errors:
|
|
8226
|
+
errors: replayErrors
|
|
8223
8227
|
};
|
|
8224
8228
|
} catch (e) {
|
|
8225
8229
|
const msg = e instanceof Error ? e.message : String(e);
|