@xbrowser/cli 0.16.0 → 1.0.2
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/README.md +17 -26
- package/dist/{browser-R7B255ML.js → browser-GITRHHFO.js} +4 -1
- package/dist/{browser-GWBH6OJK.js → browser-R56O3CW6.js} +3 -1
- package/dist/{browser-I2HJZ7IP.js → browser-ZJOZB5CR.js} +4 -2
- package/dist/cdp-driver-BE3FOMRN.js +2803 -0
- package/dist/cdp-driver-TOPYJIFL.js +47 -0
- package/dist/chunk-2SVQTI2O.js +2794 -0
- package/dist/{chunk-KDYXFLAC.js → chunk-ACFE6PKF.js} +1015 -121
- package/dist/chunk-BBMRDUYQ.js +260 -0
- package/dist/chunk-CAFNSGYM.js +4834 -0
- package/dist/{chunk-DTJRVA76.js → chunk-ETCO4SNK.js} +2 -2
- package/dist/{chunk-RS6YYWTK.js → chunk-JPA2ZT2R.js} +140 -72
- package/dist/chunk-JPHCY4TC.js +260 -0
- package/dist/chunk-KFQGP6VL.js +33 -0
- package/dist/{chunk-ITKPSIP7.js → chunk-MDAPTB7C.js} +6 -25
- package/dist/chunk-OZKD3W4X.js +417 -0
- package/dist/chunk-PPG4D2EW.js +2796 -0
- package/dist/{chunk-ATFTAKMN.js → chunk-Q4IGYTKR.js} +39 -7
- package/dist/{chunk-F3ZWFCJJ.js → chunk-QIK2I3VQ.js} +141 -72
- package/dist/chunk-WJRE55TN.js +83 -0
- package/dist/cli.js +2358 -1086
- package/dist/{convert-4DUWZIKH.js → convert-LB3GJTLR.js} +4 -2
- package/dist/{convert-EKQVHKB4.js → convert-R3XXYKC6.js} +2 -2
- package/dist/{daemon-client-GX2UYIW4.js → daemon-client-DRCUMNHK.js} +45 -72
- package/dist/{daemon-client-XWSSQBEA.js → daemon-client-UZZEHHIV.js} +8 -1
- package/dist/daemon-main.js +3067 -1688
- package/dist/{extract-JUOQQX4V.js → extract-2ZFW2MX7.js} +1 -1
- package/dist/{extract-EGRXZSSK.js → extract-BSYBM4MR.js} +2 -0
- package/dist/{filter-OLAE26HN.js → filter-KCFO4RSV.js} +2 -0
- package/dist/{filter-VID2GGZ7.js → filter-T7DSZ2X7.js} +1 -1
- package/dist/{human-interaction-W753RVJB.js → human-interaction-UKAS5ZXV.js} +2 -2
- package/dist/index.d.ts +745 -148
- package/dist/index.js +3488 -1719
- package/dist/launcher-QUJ4M2VS.js +19 -0
- package/dist/launcher-YARP45UY.js +19 -0
- package/dist/{network-store-YAF5OIBH.js → network-store-XGZ25FFC.js} +1 -0
- package/dist/{network-store-BN6QEZ7R.js → network-store-YVDNUREI.js} +1 -1
- package/dist/{parse-action-dsl-T3DYC33D.js → parse-action-dsl-UM333TL2.js} +1 -1
- package/dist/{proxy-WKGUCH2C.js → proxy-LV4BJ5RC.js} +1 -1
- package/dist/session-recorder-RTDGURIJ.js +8 -0
- package/dist/session-recorder-YI7YYM36.js +7 -0
- package/dist/session-replayer-GLTUICSD.js +276 -0
- package/dist/site-knowledge-SYC6VCDB.js +23 -0
- package/package.json +6 -6
- package/dist/chunk-2ONMTDLK.js +0 -2050
- package/dist/daemon-client-3IJD6X4B.js +0 -59
- package/dist/network-store-2S5HATEV.js +0 -194
- package/dist/parse-action-dsl-DRSPBALP.js +0 -72
- package/dist/screenshot-CWAWMXVA.js +0 -28
- package/dist/screenshot-MB6R7RSS.js +0 -26
- package/dist/session-recorder-ILSSV2UC.js +0 -6
- package/dist/session-recorder-XET3DNML.js +0 -7
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
6
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
7
|
+
}) : x)(function(x) {
|
|
8
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
9
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
10
|
+
});
|
|
11
|
+
var __esm = (fn, res) => function __init() {
|
|
12
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
13
|
+
};
|
|
14
|
+
var __export = (target, all) => {
|
|
15
|
+
for (var name in all)
|
|
16
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
17
|
+
};
|
|
18
|
+
var __copyProps = (to, from, except, desc) => {
|
|
19
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
20
|
+
for (let key of __getOwnPropNames(from))
|
|
21
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
22
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
23
|
+
}
|
|
24
|
+
return to;
|
|
25
|
+
};
|
|
26
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
27
|
+
|
|
28
|
+
export {
|
|
29
|
+
__require,
|
|
30
|
+
__esm,
|
|
31
|
+
__export,
|
|
32
|
+
__toCommonJS
|
|
33
|
+
};
|
|
@@ -276,35 +276,17 @@ var WebhookNotifier = class {
|
|
|
276
276
|
};
|
|
277
277
|
|
|
278
278
|
// src/config.ts
|
|
279
|
-
import { existsSync, mkdirSync, writeFileSync as writeFileSync2 } from "fs";
|
|
280
|
-
import { join } from "path";
|
|
281
279
|
import { homedir, tmpdir } from "os";
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
try {
|
|
287
|
-
const content = readFileSync(filePath, "utf-8");
|
|
288
|
-
return JSON.parse(content);
|
|
289
|
-
} catch {
|
|
290
|
-
return defaultValue;
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
// src/config.ts
|
|
295
|
-
function getConfigFile() {
|
|
296
|
-
return join(homedir() || tmpdir(), ".xbrowser", "config.json");
|
|
280
|
+
import { join } from "path";
|
|
281
|
+
import { loadConfig as coreLoadConfig, saveConfig as coreSaveConfig } from "@dyyz1993/xcli-core";
|
|
282
|
+
function getConfigSource() {
|
|
283
|
+
return { configDir: join(homedir() || tmpdir(), ".xbrowser") };
|
|
297
284
|
}
|
|
298
285
|
function loadConfig() {
|
|
299
|
-
|
|
300
|
-
if (!existsSync(configFile)) return {};
|
|
301
|
-
return readJsonFile(configFile, {});
|
|
286
|
+
return coreLoadConfig(getConfigSource());
|
|
302
287
|
}
|
|
303
288
|
function saveConfig(config) {
|
|
304
|
-
|
|
305
|
-
const configFile = getConfigFile();
|
|
306
|
-
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
307
|
-
writeFileSync2(configFile, JSON.stringify(config, null, 2), "utf-8");
|
|
289
|
+
coreSaveConfig(getConfigSource(), config);
|
|
308
290
|
}
|
|
309
291
|
function getConfigValue(key) {
|
|
310
292
|
return loadConfig()[key];
|
|
@@ -510,7 +492,6 @@ var HumanInteractionManager = class {
|
|
|
510
492
|
};
|
|
511
493
|
|
|
512
494
|
export {
|
|
513
|
-
readJsonFile,
|
|
514
495
|
ScreencastCapturer,
|
|
515
496
|
CaptchaDetector,
|
|
516
497
|
WebhookNotifier,
|
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
import {
|
|
2
|
+
__esm,
|
|
3
|
+
__export,
|
|
4
|
+
__require
|
|
5
|
+
} from "./chunk-KFQGP6VL.js";
|
|
6
|
+
|
|
7
|
+
// src/recorder/site-knowledge.ts
|
|
8
|
+
var site_knowledge_exports = {};
|
|
9
|
+
__export(site_knowledge_exports, {
|
|
10
|
+
addKnownIssue: () => addKnownIssue,
|
|
11
|
+
getKnowledgeDir: () => getKnowledgeDir,
|
|
12
|
+
getKnowledgePath: () => getKnowledgePath,
|
|
13
|
+
listSiteKnowledge: () => listSiteKnowledge,
|
|
14
|
+
readSiteKnowledge: () => readSiteKnowledge,
|
|
15
|
+
readSiteKnowledgeMarkdown: () => readSiteKnowledgeMarkdown,
|
|
16
|
+
toMarkdown: () => toMarkdown,
|
|
17
|
+
updateSiteKnowledge: () => updateSiteKnowledge
|
|
18
|
+
});
|
|
19
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
|
|
20
|
+
import { join } from "path";
|
|
21
|
+
import { homedir } from "os";
|
|
22
|
+
function getKnowledgeDir() {
|
|
23
|
+
return join(homedir(), ".xbrowser", "knowledge");
|
|
24
|
+
}
|
|
25
|
+
function getKnowledgePath(domain, ext) {
|
|
26
|
+
return join(getKnowledgeDir(), `${domain}.${ext}`);
|
|
27
|
+
}
|
|
28
|
+
function extractDomain(url) {
|
|
29
|
+
try {
|
|
30
|
+
const u = new URL(url);
|
|
31
|
+
return u.hostname.replace(/^www\./, "");
|
|
32
|
+
} catch {
|
|
33
|
+
return "unknown";
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function normalizePath(url) {
|
|
37
|
+
try {
|
|
38
|
+
const u = new URL(url);
|
|
39
|
+
return u.pathname;
|
|
40
|
+
} catch {
|
|
41
|
+
return url;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function extractSelectors(actions, pageUrl) {
|
|
45
|
+
const seen = /* @__PURE__ */ new Map();
|
|
46
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
47
|
+
for (const action of actions) {
|
|
48
|
+
if (normalizePath(action.url) !== normalizePath(pageUrl)) continue;
|
|
49
|
+
const el = action.element;
|
|
50
|
+
if (!el || !el.selector) continue;
|
|
51
|
+
const key = el.selector;
|
|
52
|
+
const existing = seen.get(key);
|
|
53
|
+
if (existing) {
|
|
54
|
+
existing.timesSeen++;
|
|
55
|
+
existing.lastSeen = now;
|
|
56
|
+
} else {
|
|
57
|
+
const description = buildDescription(el, action);
|
|
58
|
+
seen.set(key, {
|
|
59
|
+
selector: key,
|
|
60
|
+
tag: el.tag || "unknown",
|
|
61
|
+
description,
|
|
62
|
+
actionType: action.type,
|
|
63
|
+
role: el.role,
|
|
64
|
+
text: el.text?.substring(0, 60),
|
|
65
|
+
confidence: el.confidence || "medium",
|
|
66
|
+
lastSeen: now,
|
|
67
|
+
timesSeen: 1,
|
|
68
|
+
status: "active"
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return Array.from(seen.values()).sort((a, b) => b.timesSeen - a.timesSeen);
|
|
73
|
+
}
|
|
74
|
+
function buildDescription(el, action) {
|
|
75
|
+
const parts = [];
|
|
76
|
+
if (el.text) parts.push(`"${el.text}"`);
|
|
77
|
+
if (el.placeholder) parts.push(`placeholder="${el.placeholder}"`);
|
|
78
|
+
if (el.ariaLabel) parts.push(`aria-label="${el.ariaLabel}"`);
|
|
79
|
+
if (el.role) parts.push(`role=${el.role}`);
|
|
80
|
+
if (el.type) parts.push(`type=${el.type}`);
|
|
81
|
+
const actionDesc = {
|
|
82
|
+
click: "clicked",
|
|
83
|
+
input: `filled with "${action.value?.substring(0, 30)}"`,
|
|
84
|
+
change: "changed",
|
|
85
|
+
submit: "submitted form",
|
|
86
|
+
dblclick: "double-clicked",
|
|
87
|
+
contextmenu: "right-clicked",
|
|
88
|
+
hover: "hovered over",
|
|
89
|
+
focus: "focused"
|
|
90
|
+
};
|
|
91
|
+
const verb = actionDesc[action.type] || action.type;
|
|
92
|
+
const base = parts.length > 0 ? parts.join(", ") : el.tag || "element";
|
|
93
|
+
return `${base} \u2014 ${verb}`;
|
|
94
|
+
}
|
|
95
|
+
function extractForms(actions, pageUrl) {
|
|
96
|
+
const forms = /* @__PURE__ */ new Map();
|
|
97
|
+
for (const action of actions) {
|
|
98
|
+
if (normalizePath(action.url) !== normalizePath(pageUrl)) continue;
|
|
99
|
+
const el = action.element;
|
|
100
|
+
if (!el) continue;
|
|
101
|
+
if (el.tag === "input" || el.tag === "textarea" || el.tag === "select") {
|
|
102
|
+
const formKey = "main";
|
|
103
|
+
const form = forms.get(formKey) || {
|
|
104
|
+
name: "Main Form",
|
|
105
|
+
action: pageUrl,
|
|
106
|
+
fields: []
|
|
107
|
+
};
|
|
108
|
+
if (!form.fields.some((f) => f.selector === el.selector)) {
|
|
109
|
+
form.fields.push({
|
|
110
|
+
selector: el.selector || "",
|
|
111
|
+
tag: el.tag,
|
|
112
|
+
label: el.ariaLabel || el.placeholder || el.text || el.selector || "",
|
|
113
|
+
inputType: el.type || el.tag,
|
|
114
|
+
placeholder: el.placeholder
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
forms.set(formKey, form);
|
|
118
|
+
}
|
|
119
|
+
if (action.type === "submit" || action.type === "click" && el.tag === "button" && el.text) {
|
|
120
|
+
const form = forms.get("main");
|
|
121
|
+
if (form && !form.submitSelector) {
|
|
122
|
+
form.submitSelector = el.selector;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return Array.from(forms.values());
|
|
127
|
+
}
|
|
128
|
+
function extractNavLinks(actions) {
|
|
129
|
+
const links = [];
|
|
130
|
+
const seen = /* @__PURE__ */ new Set();
|
|
131
|
+
for (const action of actions) {
|
|
132
|
+
if (action.type !== "click" && action.type !== "navigation") continue;
|
|
133
|
+
const el = action.element;
|
|
134
|
+
if (!el || el.tag !== "a" || !el.text) continue;
|
|
135
|
+
const href = el.href || action.url;
|
|
136
|
+
if (!href || seen.has(href)) continue;
|
|
137
|
+
seen.add(href);
|
|
138
|
+
links.push({
|
|
139
|
+
text: el.text.substring(0, 40),
|
|
140
|
+
href,
|
|
141
|
+
selector: el.selector || ""
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
return links;
|
|
145
|
+
}
|
|
146
|
+
function extractApiEndpoints(network, existingEndpoints) {
|
|
147
|
+
const endpoints = {};
|
|
148
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
149
|
+
if (existingEndpoints) {
|
|
150
|
+
for (const [key, ep] of Object.entries(existingEndpoints)) {
|
|
151
|
+
endpoints[key] = ep;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
for (const entry of network) {
|
|
155
|
+
if (!entry.url.includes("/api/") && !entry.url.includes("/v1/") && !entry.url.includes("/v2/") && entry.contentType && !entry.contentType.includes("json") && !entry.contentType.includes("text/")) continue;
|
|
156
|
+
if (["image", "stylesheet", "font", "manifest"].includes(entry.resourceType)) continue;
|
|
157
|
+
let path;
|
|
158
|
+
try {
|
|
159
|
+
path = new URL(entry.url).pathname;
|
|
160
|
+
} catch {
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
const method = entry.method;
|
|
164
|
+
const key = `${method} ${path}`;
|
|
165
|
+
if (endpoints[key]) {
|
|
166
|
+
endpoints[key].timesSeen++;
|
|
167
|
+
endpoints[key].lastSeen = now;
|
|
168
|
+
} else {
|
|
169
|
+
let params = [];
|
|
170
|
+
if (entry.requestBody && typeof entry.requestBody === "object") {
|
|
171
|
+
params = Object.keys(entry.requestBody).slice(0, 10);
|
|
172
|
+
}
|
|
173
|
+
let responseFields = [];
|
|
174
|
+
if (entry.responseBody && typeof entry.responseBody === "object") {
|
|
175
|
+
const resp = entry.responseBody;
|
|
176
|
+
responseFields = Object.keys(resp).slice(0, 10);
|
|
177
|
+
if (resp.data && typeof resp.data === "object") {
|
|
178
|
+
const dataKeys = Object.keys(resp.data).slice(0, 10);
|
|
179
|
+
responseFields = [...responseFields, ...dataKeys.map((k) => `data.${k}`)];
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
endpoints[key] = {
|
|
183
|
+
method,
|
|
184
|
+
url: entry.url.substring(0, 200),
|
|
185
|
+
path,
|
|
186
|
+
params,
|
|
187
|
+
responseFields,
|
|
188
|
+
lastSeen: now,
|
|
189
|
+
timesSeen: 1
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return endpoints;
|
|
194
|
+
}
|
|
195
|
+
function mergePages(existing, newPages) {
|
|
196
|
+
const merged = { ...existing };
|
|
197
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
198
|
+
for (const [path, newPage] of Object.entries(newPages)) {
|
|
199
|
+
if (merged[path]) {
|
|
200
|
+
const old = merged[path];
|
|
201
|
+
const selectorMap = /* @__PURE__ */ new Map();
|
|
202
|
+
for (const sel of old.selectors) selectorMap.set(sel.selector, sel);
|
|
203
|
+
for (const sel of newPage.selectors) {
|
|
204
|
+
const existing2 = selectorMap.get(sel.selector);
|
|
205
|
+
if (existing2) {
|
|
206
|
+
existing2.timesSeen += sel.timesSeen;
|
|
207
|
+
existing2.lastSeen = now;
|
|
208
|
+
existing2.status = "active";
|
|
209
|
+
} else {
|
|
210
|
+
selectorMap.set(sel.selector, sel);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
const newSelectorSet = new Set(newPage.selectors.map((s) => s.selector));
|
|
214
|
+
for (const sel of selectorMap.values()) {
|
|
215
|
+
if (!newSelectorSet.has(sel.selector) && sel.timesSeen > 0) {
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
merged[path] = {
|
|
219
|
+
...newPage,
|
|
220
|
+
selectors: Array.from(selectorMap.values()).sort((a, b) => b.timesSeen - a.timesSeen),
|
|
221
|
+
forms: newPage.forms.length > 0 ? newPage.forms : old.forms,
|
|
222
|
+
navigationLinks: [.../* @__PURE__ */ new Set([...old.navigationLinks, ...newPage.navigationLinks])].slice(0, 50),
|
|
223
|
+
lastVisited: now
|
|
224
|
+
};
|
|
225
|
+
} else {
|
|
226
|
+
merged[path] = newPage;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return merged;
|
|
230
|
+
}
|
|
231
|
+
function updateSiteKnowledge(data) {
|
|
232
|
+
const domain = extractDomain(data.startUrl);
|
|
233
|
+
const jsonPath = getKnowledgePath(domain, "json");
|
|
234
|
+
let existing = null;
|
|
235
|
+
if (existsSync(jsonPath)) {
|
|
236
|
+
try {
|
|
237
|
+
existing = JSON.parse(readFileSync(jsonPath, "utf-8"));
|
|
238
|
+
} catch {
|
|
239
|
+
existing = null;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
const pageMap = /* @__PURE__ */ new Map();
|
|
243
|
+
for (const action of data.actions) {
|
|
244
|
+
const path = normalizePath(action.url);
|
|
245
|
+
if (!pageMap.has(path)) pageMap.set(path, []);
|
|
246
|
+
pageMap.get(path).push(action);
|
|
247
|
+
}
|
|
248
|
+
const newPages = {};
|
|
249
|
+
for (const [path, actions] of pageMap) {
|
|
250
|
+
const fullUrl = actions[0]?.url || data.startUrl;
|
|
251
|
+
newPages[path] = {
|
|
252
|
+
url: fullUrl,
|
|
253
|
+
title: actions[0]?.pageTitle || "",
|
|
254
|
+
selectors: extractSelectors(data.actions, fullUrl),
|
|
255
|
+
forms: extractForms(data.actions, fullUrl),
|
|
256
|
+
navigationLinks: extractNavLinks(actions),
|
|
257
|
+
lastVisited: (/* @__PURE__ */ new Date()).toISOString()
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
const apiEndpoints = extractApiEndpoints(data.network, existing?.apiEndpoints);
|
|
261
|
+
const pages = existing ? mergePages(existing.pages, newPages) : newPages;
|
|
262
|
+
const knowledge = {
|
|
263
|
+
domain,
|
|
264
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
265
|
+
recordingCount: (existing?.recordingCount || 0) + 1,
|
|
266
|
+
pages,
|
|
267
|
+
apiEndpoints,
|
|
268
|
+
knownIssues: existing?.knownIssues || [],
|
|
269
|
+
generatedBy: "xbrowser-recorder"
|
|
270
|
+
};
|
|
271
|
+
mkdirSync(getKnowledgeDir(), { recursive: true });
|
|
272
|
+
writeFileSync(jsonPath, JSON.stringify(knowledge, null, 2), "utf-8");
|
|
273
|
+
writeFileSync(getKnowledgePath(domain, "md"), toMarkdown(knowledge), "utf-8");
|
|
274
|
+
return knowledge;
|
|
275
|
+
}
|
|
276
|
+
function toMarkdown(kb) {
|
|
277
|
+
const lines = [];
|
|
278
|
+
lines.push(`# Site Knowledge: ${kb.domain}`);
|
|
279
|
+
lines.push("");
|
|
280
|
+
lines.push("> **Auto-generated by xbrowser recorder. This document is for LLM consumption.**");
|
|
281
|
+
lines.push("> Use these selectors when writing automation scripts for this site.");
|
|
282
|
+
lines.push(`> Updated: ${kb.lastUpdated} | Recordings: ${kb.recordingCount}`);
|
|
283
|
+
lines.push("");
|
|
284
|
+
lines.push("## Pages");
|
|
285
|
+
lines.push("");
|
|
286
|
+
for (const page of Object.values(kb.pages)) {
|
|
287
|
+
lines.push(`### ${page.url}`);
|
|
288
|
+
lines.push(`- **Path**: ${normalizePath(page.url)}`);
|
|
289
|
+
if (page.title) lines.push(`- **Title**: ${page.title}`);
|
|
290
|
+
lines.push(`- **Last Visited**: ${page.lastVisited}`);
|
|
291
|
+
lines.push("");
|
|
292
|
+
if (page.selectors.length > 0) {
|
|
293
|
+
lines.push("#### Selectors");
|
|
294
|
+
lines.push("");
|
|
295
|
+
lines.push("| Selector | Tag | Action | Description | Confidence | Seen |");
|
|
296
|
+
lines.push("|----------|-----|--------|-------------|------------|------|");
|
|
297
|
+
for (const sel of page.selectors) {
|
|
298
|
+
const status = sel.status === "deprecated" ? " \u26A0\uFE0FDEPRECATED" : "";
|
|
299
|
+
lines.push(
|
|
300
|
+
`| \`${sel.selector}\` | ${sel.tag} | ${sel.actionType} | ${sel.description} | ${sel.confidence} | ${sel.timesSeen}x${status} |`
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
lines.push("");
|
|
304
|
+
}
|
|
305
|
+
if (page.forms.length > 0) {
|
|
306
|
+
lines.push("#### Forms");
|
|
307
|
+
lines.push("");
|
|
308
|
+
for (const form of page.forms) {
|
|
309
|
+
lines.push(`- **${form.name}** (${form.action})`);
|
|
310
|
+
for (const field of form.fields) {
|
|
311
|
+
const parts = [field.tag, field.inputType];
|
|
312
|
+
if (field.placeholder) parts.push(`placeholder="${field.placeholder}"`);
|
|
313
|
+
lines.push(` - \`${field.selector}\` \u2192 ${field.label} (${parts.join(", ")})`);
|
|
314
|
+
}
|
|
315
|
+
if (form.submitSelector) {
|
|
316
|
+
lines.push(` - Submit: \`${form.submitSelector}\``);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
lines.push("");
|
|
320
|
+
}
|
|
321
|
+
if (page.navigationLinks.length > 0) {
|
|
322
|
+
lines.push("#### Navigation Links");
|
|
323
|
+
lines.push("");
|
|
324
|
+
for (const link of page.navigationLinks.slice(0, 20)) {
|
|
325
|
+
lines.push(`- [${link.text}](${link.href}) \u2192 \`${link.selector}\``);
|
|
326
|
+
}
|
|
327
|
+
lines.push("");
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
const endpoints = Object.values(kb.apiEndpoints);
|
|
331
|
+
if (endpoints.length > 0) {
|
|
332
|
+
lines.push("## API Endpoints");
|
|
333
|
+
lines.push("");
|
|
334
|
+
lines.push("| Method | Path | Params | Response Fields | Frequency |");
|
|
335
|
+
lines.push("|--------|------|--------|-----------------|-----------|");
|
|
336
|
+
for (const ep of endpoints.sort((a, b) => b.timesSeen - a.timesSeen)) {
|
|
337
|
+
const params = ep.params.length > 0 ? ep.params.join(", ") : "-";
|
|
338
|
+
const respFields = ep.responseFields.length > 0 ? ep.responseFields.slice(0, 5).join(", ") : "-";
|
|
339
|
+
lines.push(`| ${ep.method} | ${ep.path} | ${params} | ${respFields} | ${ep.timesSeen}x |`);
|
|
340
|
+
}
|
|
341
|
+
lines.push("");
|
|
342
|
+
}
|
|
343
|
+
if (kb.knownIssues.length > 0) {
|
|
344
|
+
lines.push("## Known Issues");
|
|
345
|
+
lines.push("");
|
|
346
|
+
for (const issue of kb.knownIssues) {
|
|
347
|
+
lines.push(`- ${issue}`);
|
|
348
|
+
}
|
|
349
|
+
lines.push("");
|
|
350
|
+
}
|
|
351
|
+
lines.push("---");
|
|
352
|
+
lines.push("");
|
|
353
|
+
lines.push("## How to Use This Document");
|
|
354
|
+
lines.push("");
|
|
355
|
+
lines.push("When writing automation scripts for this site:");
|
|
356
|
+
lines.push("1. Use the selectors from the **Selectors** tables above");
|
|
357
|
+
lines.push("2. Prefer selectors with **high confidence** and **higher Seen count**");
|
|
358
|
+
lines.push("3. For form filling, follow the **Forms** structure");
|
|
359
|
+
lines.push("4. For API interactions, reference the **API Endpoints** table");
|
|
360
|
+
lines.push("5. If a selector fails, it may be **deprecated** \u2014 check for alternative selectors");
|
|
361
|
+
lines.push("");
|
|
362
|
+
return lines.join("\n");
|
|
363
|
+
}
|
|
364
|
+
function readSiteKnowledge(domain) {
|
|
365
|
+
const path = getKnowledgePath(domain, "json");
|
|
366
|
+
if (!existsSync(path)) return null;
|
|
367
|
+
try {
|
|
368
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
369
|
+
} catch {
|
|
370
|
+
return null;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
function readSiteKnowledgeMarkdown(domain) {
|
|
374
|
+
const path = getKnowledgePath(domain, "md");
|
|
375
|
+
if (!existsSync(path)) return null;
|
|
376
|
+
try {
|
|
377
|
+
return readFileSync(path, "utf-8");
|
|
378
|
+
} catch {
|
|
379
|
+
return null;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
function listSiteKnowledge() {
|
|
383
|
+
const dir = getKnowledgeDir();
|
|
384
|
+
if (!existsSync(dir)) return [];
|
|
385
|
+
try {
|
|
386
|
+
const files = __require("fs").readdirSync(dir);
|
|
387
|
+
return files.filter((f) => f.endsWith(".json")).map((f) => f.replace(".json", ""));
|
|
388
|
+
} catch {
|
|
389
|
+
return [];
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
function addKnownIssue(domain, issue) {
|
|
393
|
+
const kb = readSiteKnowledge(domain);
|
|
394
|
+
if (!kb) return;
|
|
395
|
+
const dated = `[${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}] ${issue}`;
|
|
396
|
+
kb.knownIssues.push(dated);
|
|
397
|
+
kb.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
|
|
398
|
+
writeFileSync(getKnowledgePath(domain, "json"), JSON.stringify(kb, null, 2), "utf-8");
|
|
399
|
+
writeFileSync(getKnowledgePath(domain, "md"), toMarkdown(kb), "utf-8");
|
|
400
|
+
}
|
|
401
|
+
var init_site_knowledge = __esm({
|
|
402
|
+
"src/recorder/site-knowledge.ts"() {
|
|
403
|
+
}
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
export {
|
|
407
|
+
getKnowledgeDir,
|
|
408
|
+
getKnowledgePath,
|
|
409
|
+
updateSiteKnowledge,
|
|
410
|
+
toMarkdown,
|
|
411
|
+
readSiteKnowledge,
|
|
412
|
+
readSiteKnowledgeMarkdown,
|
|
413
|
+
listSiteKnowledge,
|
|
414
|
+
addKnownIssue,
|
|
415
|
+
site_knowledge_exports,
|
|
416
|
+
init_site_knowledge
|
|
417
|
+
};
|