@xbrowser/cli 1.2.0 → 1.2.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/dist/{chunk-XYXCS7JW.js → chunk-JPSFUFPG.js} +4 -2
- package/dist/chunk-PHBK3TRN.js +436 -0
- package/dist/cli.js +52 -37
- package/dist/{daemon-client-R4QWHD7V.js → daemon-client-COJQESU2.js} +4 -2
- package/dist/{daemon-client-ZHO6NG36.js → daemon-client-XXKMJZZ7.js} +1 -1
- package/dist/daemon-main.js +132 -524
- package/dist/index.js +52 -37
- package/dist/plugin-singleton-ZBVTWEYK.js +9 -0
- package/package.json +1 -1
|
@@ -279,8 +279,10 @@ async function forwardNetworkInspect(sessionName, id) {
|
|
|
279
279
|
async function forwardRecordStart(session, url, cdpEndpoint) {
|
|
280
280
|
return rpcCall("record:start", { session, url, cdpEndpoint }, 15e3);
|
|
281
281
|
}
|
|
282
|
-
async function forwardRecordStop(session) {
|
|
283
|
-
|
|
282
|
+
async function forwardRecordStop(session, output) {
|
|
283
|
+
const params = { session };
|
|
284
|
+
if (output) params.output = output;
|
|
285
|
+
return rpcCall("record:stop", params, 1e4);
|
|
284
286
|
}
|
|
285
287
|
async function forwardRecordStatus(session) {
|
|
286
288
|
return rpcCall("record:status", { session }, 5e3);
|
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
// src/plugin/loader.ts
|
|
2
|
+
import {
|
|
3
|
+
Core
|
|
4
|
+
} from "@dyyz1993/xcli-core";
|
|
5
|
+
import { resolve as resolve2 } from "path";
|
|
6
|
+
import { existsSync as existsSync3, readdirSync } from "fs";
|
|
7
|
+
import { homedir } from "os";
|
|
8
|
+
|
|
9
|
+
// src/plugin/metadata-parser.ts
|
|
10
|
+
import { existsSync } from "fs";
|
|
11
|
+
import { resolve } from "path";
|
|
12
|
+
|
|
13
|
+
// src/utils/json-file.ts
|
|
14
|
+
import { readFileSync, writeFileSync } from "fs";
|
|
15
|
+
function readJsonFile(filePath, defaultValue) {
|
|
16
|
+
try {
|
|
17
|
+
const content = readFileSync(filePath, "utf-8");
|
|
18
|
+
return JSON.parse(content);
|
|
19
|
+
} catch {
|
|
20
|
+
return defaultValue;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// src/plugin/metadata-parser.ts
|
|
25
|
+
var PluginMetadataParser = class {
|
|
26
|
+
static XBROWSER_KEYWORDS = ["xbrowser", "xbrowser-plugin"];
|
|
27
|
+
static parseFromPackageJson(pluginPath) {
|
|
28
|
+
const packageJsonPath = resolve(pluginPath, "package.json");
|
|
29
|
+
if (!existsSync(packageJsonPath)) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
const packageJson = readJsonFile(packageJsonPath, null);
|
|
33
|
+
if (!packageJson) return null;
|
|
34
|
+
if (!packageJson.xbrowser) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
const xbrowser = packageJson.xbrowser;
|
|
38
|
+
const metadata = {
|
|
39
|
+
id: xbrowser.id || packageJson.name,
|
|
40
|
+
name: xbrowser.name || packageJson.name,
|
|
41
|
+
description: xbrowser.description || packageJson.description || "",
|
|
42
|
+
version: xbrowser.version || packageJson.version || "1.0.0",
|
|
43
|
+
author: xbrowser.author || this.extractAuthor(packageJson.author),
|
|
44
|
+
homepage: xbrowser.homepage || packageJson.homepage,
|
|
45
|
+
commands: xbrowser.commands,
|
|
46
|
+
sites: xbrowser.sites,
|
|
47
|
+
tags: xbrowser.tags,
|
|
48
|
+
screenshot: xbrowser.screenshot,
|
|
49
|
+
license: xbrowser.license || packageJson.license
|
|
50
|
+
};
|
|
51
|
+
return metadata;
|
|
52
|
+
}
|
|
53
|
+
static isXBrowserPlugin(packageJson) {
|
|
54
|
+
if (packageJson.xbrowser) {
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
const keywords = packageJson.keywords;
|
|
58
|
+
if (!keywords) return false;
|
|
59
|
+
return this.XBROWSER_KEYWORDS.some((kw) => keywords.includes(kw));
|
|
60
|
+
}
|
|
61
|
+
static fromNPMResult(result) {
|
|
62
|
+
const author = typeof result.author === "string" ? result.author : result.author?.name || "Unknown";
|
|
63
|
+
return {
|
|
64
|
+
id: result.name,
|
|
65
|
+
name: result.name.replace(/^xbrowser-plugin-/, "").replace(/^@[^/]+\//, ""),
|
|
66
|
+
description: result.description || "",
|
|
67
|
+
version: result.version,
|
|
68
|
+
author,
|
|
69
|
+
homepage: result.homepage || result.links?.homepage,
|
|
70
|
+
tags: result.keywords,
|
|
71
|
+
license: ""
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
static extractAuthor(author) {
|
|
75
|
+
if (typeof author === "string") return author;
|
|
76
|
+
if (typeof author === "object" && author !== null) {
|
|
77
|
+
const authorObj = author;
|
|
78
|
+
return authorObj.name || "Unknown";
|
|
79
|
+
}
|
|
80
|
+
return "Unknown";
|
|
81
|
+
}
|
|
82
|
+
static validateMetadata(metadata) {
|
|
83
|
+
const errors = [];
|
|
84
|
+
if (!metadata.id) errors.push("id is required");
|
|
85
|
+
if (!metadata.name) errors.push("name is required");
|
|
86
|
+
if (!metadata.description) errors.push("description is required");
|
|
87
|
+
if (!metadata.version) errors.push("version is required");
|
|
88
|
+
return errors;
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
// src/plugin/ensure-deps.ts
|
|
93
|
+
import { existsSync as existsSync2, mkdirSync, writeFileSync as writeFileSync2 } from "fs";
|
|
94
|
+
import { join } from "path";
|
|
95
|
+
import { execSync } from "child_process";
|
|
96
|
+
var SHARED_PLUGIN_DEPENDENCIES = {
|
|
97
|
+
"zod": "^3.24.0",
|
|
98
|
+
"@dyyz1993/xcli-core": "^0.12.1"
|
|
99
|
+
};
|
|
100
|
+
function ensurePluginDependencies(pluginsDir) {
|
|
101
|
+
const zodPath = join(pluginsDir, "node_modules", "zod");
|
|
102
|
+
if (existsSync2(zodPath)) return;
|
|
103
|
+
mkdirSync(pluginsDir, { recursive: true });
|
|
104
|
+
const pkgPath = join(pluginsDir, "package.json");
|
|
105
|
+
let pkg = {};
|
|
106
|
+
if (existsSync2(pkgPath)) {
|
|
107
|
+
try {
|
|
108
|
+
pkg = readJsonFile(pkgPath, {});
|
|
109
|
+
} catch {
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
const existingDeps = pkg.dependencies || {};
|
|
113
|
+
let needsInstall = false;
|
|
114
|
+
for (const [dep, version] of Object.entries(SHARED_PLUGIN_DEPENDENCIES)) {
|
|
115
|
+
if (!existingDeps[dep]) {
|
|
116
|
+
existingDeps[dep] = version;
|
|
117
|
+
needsInstall = true;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (!needsInstall && existsSync2(join(pluginsDir, "node_modules"))) return;
|
|
121
|
+
pkg.dependencies = existingDeps;
|
|
122
|
+
pkg.private = true;
|
|
123
|
+
pkg.description = pkg.description || "xbrowser plugins \u2014 shared dependencies";
|
|
124
|
+
writeFileSync2(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf-8");
|
|
125
|
+
try {
|
|
126
|
+
execSync("npm install --production --no-package-lock --no-fund --no-audit", {
|
|
127
|
+
cwd: pluginsDir,
|
|
128
|
+
stdio: "pipe",
|
|
129
|
+
timeout: 6e4,
|
|
130
|
+
env: { ...process.env, NODE_ENV: "production" }
|
|
131
|
+
});
|
|
132
|
+
} catch (err) {
|
|
133
|
+
console.warn(`\u26A0\uFE0F Failed to install shared plugin dependencies: ${err instanceof Error ? err.message : String(err)}`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// src/plugin/contract.ts
|
|
138
|
+
import {
|
|
139
|
+
unwrapZod,
|
|
140
|
+
fieldsFromZodObjectReflected,
|
|
141
|
+
zodTypeToContractType
|
|
142
|
+
} from "@dyyz1993/xcli-core";
|
|
143
|
+
function buildPluginContract(site) {
|
|
144
|
+
const commands = site.getAllCommands().map((command) => buildCommandContract(site.getCommand?.(command.name) || command, {
|
|
145
|
+
siteRequiresLogin: site.config?.requiresLogin
|
|
146
|
+
}));
|
|
147
|
+
return {
|
|
148
|
+
version: 2,
|
|
149
|
+
plugin: {
|
|
150
|
+
name: site.name,
|
|
151
|
+
url: site.url,
|
|
152
|
+
description: site.config?.description,
|
|
153
|
+
requiresLogin: site.config?.requiresLogin
|
|
154
|
+
},
|
|
155
|
+
commands
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
function buildCommandContract(command, options = {}) {
|
|
159
|
+
const extension = command.xbrowser || {};
|
|
160
|
+
const inferredFields = fieldsFromZodObject(command.parameters);
|
|
161
|
+
const fields = mergeFields(inferredFields, extension.form?.fields || []);
|
|
162
|
+
const positional = extension.positional || fields.filter((field) => field.positional).map((field) => field.name);
|
|
163
|
+
const requiresLogin = command.requiresLogin === true || options.siteRequiresLogin === true && command.name !== "login" && command.name !== "logout";
|
|
164
|
+
const capabilities = extension.capabilities || inferCapabilities(command.scope || "project", requiresLogin);
|
|
165
|
+
const outputSchema = command.result ? summarizeZod(command.result) : void 0;
|
|
166
|
+
return {
|
|
167
|
+
name: command.name,
|
|
168
|
+
description: command.description || "",
|
|
169
|
+
scope: command.scope || "project",
|
|
170
|
+
requiresLogin,
|
|
171
|
+
category: extension.category,
|
|
172
|
+
capabilities,
|
|
173
|
+
positional,
|
|
174
|
+
form: {
|
|
175
|
+
title: extension.form?.title || command.description || command.name,
|
|
176
|
+
description: extension.form?.description,
|
|
177
|
+
submitLabel: extension.form?.submitLabel || "Run",
|
|
178
|
+
fields
|
|
179
|
+
},
|
|
180
|
+
output: extension.output || (outputSchema ? { schema: outputSchema } : void 0)
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
function fieldsFromZodObject(schema) {
|
|
184
|
+
const reflected = fieldsFromZodObjectReflected(schema);
|
|
185
|
+
return reflected.map((field) => {
|
|
186
|
+
const widget = widgetFor(field.type, field.enum);
|
|
187
|
+
return {
|
|
188
|
+
name: field.name,
|
|
189
|
+
label: toLabel(field.name),
|
|
190
|
+
type: field.type,
|
|
191
|
+
widget,
|
|
192
|
+
required: field.required,
|
|
193
|
+
...field.description ? { description: field.description } : {},
|
|
194
|
+
...field.default !== void 0 ? { default: field.default } : {},
|
|
195
|
+
...field.enum ? { enum: field.enum } : {},
|
|
196
|
+
...field.type === "array" ? { multiple: true } : {}
|
|
197
|
+
};
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
function mergeFields(inferred, overrides) {
|
|
201
|
+
if (overrides.length === 0) return inferred;
|
|
202
|
+
const byName = new Map(inferred.map((field) => [field.name, field]));
|
|
203
|
+
const seen = /* @__PURE__ */ new Set();
|
|
204
|
+
const merged = [];
|
|
205
|
+
for (const override of overrides) {
|
|
206
|
+
if (!override.name) continue;
|
|
207
|
+
const base = byName.get(override.name) || {
|
|
208
|
+
name: override.name,
|
|
209
|
+
label: toLabel(override.name),
|
|
210
|
+
type: "string",
|
|
211
|
+
widget: "text",
|
|
212
|
+
required: false
|
|
213
|
+
};
|
|
214
|
+
merged.push({ ...base, ...override, name: override.name });
|
|
215
|
+
seen.add(override.name);
|
|
216
|
+
}
|
|
217
|
+
for (const field of inferred) {
|
|
218
|
+
if (!seen.has(field.name)) merged.push(field);
|
|
219
|
+
}
|
|
220
|
+
return merged;
|
|
221
|
+
}
|
|
222
|
+
function inferCapabilities(scope, requiresLogin) {
|
|
223
|
+
const caps = [];
|
|
224
|
+
if (scope === "page") caps.push("browser.page");
|
|
225
|
+
if (scope === "browser") caps.push("browser.context");
|
|
226
|
+
if (requiresLogin) caps.push("auth.login");
|
|
227
|
+
return caps;
|
|
228
|
+
}
|
|
229
|
+
function widgetFor(type, enumValues) {
|
|
230
|
+
if (enumValues) return "select";
|
|
231
|
+
if (type === "boolean") return "checkbox";
|
|
232
|
+
if (type === "number") return "number";
|
|
233
|
+
if (type === "array") return "multi-select";
|
|
234
|
+
if (type === "object") return "json";
|
|
235
|
+
return "text";
|
|
236
|
+
}
|
|
237
|
+
function summarizeZod(schema) {
|
|
238
|
+
const unwrapped = unwrapZod(schema);
|
|
239
|
+
if (unwrapped.typeName === "ZodArray") {
|
|
240
|
+
const def = unwrapped.schema?._def;
|
|
241
|
+
return {
|
|
242
|
+
type: "array",
|
|
243
|
+
items: summarizeZod(def?.type || def?.innerType)
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
const shape = getObjectShape(schema);
|
|
247
|
+
if (!shape) {
|
|
248
|
+
return {
|
|
249
|
+
type: zodTypeToContractType(unwrapped.typeName),
|
|
250
|
+
required: !unwrapped.optional,
|
|
251
|
+
...unwrapped.description ? { description: unwrapped.description } : {}
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
return Object.fromEntries(
|
|
255
|
+
Object.entries(shape).map(([name, field]) => {
|
|
256
|
+
const inner = unwrapZod(field);
|
|
257
|
+
return [name, {
|
|
258
|
+
type: zodTypeToContractType(inner.typeName),
|
|
259
|
+
required: !inner.optional,
|
|
260
|
+
...inner.description ? { description: inner.description } : {}
|
|
261
|
+
}];
|
|
262
|
+
})
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
function getObjectShape(schema) {
|
|
266
|
+
const zod = schema;
|
|
267
|
+
const shapeOrFn = zod?.shape ?? zod?._def?.shape;
|
|
268
|
+
if (!shapeOrFn) return void 0;
|
|
269
|
+
return typeof shapeOrFn === "function" ? shapeOrFn() : shapeOrFn;
|
|
270
|
+
}
|
|
271
|
+
function toLabel(name) {
|
|
272
|
+
return name.replace(/([A-Z])/g, " $1").replace(/[-_]+/g, " ").replace(/\s+/g, " ").trim().replace(/^./, (char) => char.toUpperCase());
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// src/plugin/login-required-patch.ts
|
|
276
|
+
import { SiteInstanceImpl } from "@dyyz1993/xcli-core";
|
|
277
|
+
var patched = false;
|
|
278
|
+
function patchLoginRequired() {
|
|
279
|
+
if (patched) return;
|
|
280
|
+
patched = true;
|
|
281
|
+
const target = SiteInstanceImpl.prototype;
|
|
282
|
+
const originalCommand = target.command;
|
|
283
|
+
const wrapped = function(...args) {
|
|
284
|
+
const result = originalCommand.apply(this, args);
|
|
285
|
+
const [name, cmd] = args;
|
|
286
|
+
const loginRequired = cmd.loginRequired;
|
|
287
|
+
if (loginRequired) {
|
|
288
|
+
const commands = this.commands;
|
|
289
|
+
const entry = commands?.get(name);
|
|
290
|
+
if (entry) {
|
|
291
|
+
entry.loginRequired = loginRequired;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return result;
|
|
295
|
+
};
|
|
296
|
+
Object.defineProperty(target, "command", {
|
|
297
|
+
value: wrapped,
|
|
298
|
+
writable: true,
|
|
299
|
+
configurable: true
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// src/plugin/loader.ts
|
|
304
|
+
var DEFAULT_PLUGIN_DIRS = [".xcli/plugins", "../.xcli/plugins"];
|
|
305
|
+
var XBrowserPluginLoader = class {
|
|
306
|
+
core;
|
|
307
|
+
loader;
|
|
308
|
+
options;
|
|
309
|
+
constructor(options) {
|
|
310
|
+
patchLoginRequired();
|
|
311
|
+
this.options = options ?? {};
|
|
312
|
+
const cwd = this.options.cwd || process.cwd();
|
|
313
|
+
const coreConfig = {
|
|
314
|
+
name: "xbrowser",
|
|
315
|
+
version: "0.1.0",
|
|
316
|
+
description: "Browser automation CLI",
|
|
317
|
+
configDirName: ".xbrowser",
|
|
318
|
+
envPrefix: "XBROWSER",
|
|
319
|
+
pluginDirs: [
|
|
320
|
+
...DEFAULT_PLUGIN_DIRS,
|
|
321
|
+
resolve2(cwd, ".xcli/plugins")
|
|
322
|
+
]
|
|
323
|
+
};
|
|
324
|
+
this.core = new Core(coreConfig);
|
|
325
|
+
this.loader = this.core.loader;
|
|
326
|
+
}
|
|
327
|
+
getAPI() {
|
|
328
|
+
return this.loader.getAPI();
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Get the core instance for external use.
|
|
332
|
+
* @returns The xcli-core Core instance.
|
|
333
|
+
*/
|
|
334
|
+
getCore() {
|
|
335
|
+
return this.core;
|
|
336
|
+
}
|
|
337
|
+
getPlugin(id) {
|
|
338
|
+
return this.loader.getPlugin(id);
|
|
339
|
+
}
|
|
340
|
+
getPluginStatus(id) {
|
|
341
|
+
return this.loader.getPluginStatus(id);
|
|
342
|
+
}
|
|
343
|
+
getLoadedPlugins() {
|
|
344
|
+
return this.loader.getLoadedPlugins();
|
|
345
|
+
}
|
|
346
|
+
getPluginContract(siteName, commandName) {
|
|
347
|
+
const site = this.core.loader.getSite(siteName);
|
|
348
|
+
if (!site) return void 0;
|
|
349
|
+
const contract = buildPluginContract(site);
|
|
350
|
+
if (!commandName) return contract;
|
|
351
|
+
return contract.commands.find((command) => command.name === commandName);
|
|
352
|
+
}
|
|
353
|
+
async loadPlugin(pluginPath, id) {
|
|
354
|
+
return this.loader.loadPlugin(pluginPath, id);
|
|
355
|
+
}
|
|
356
|
+
async unloadPlugin(id) {
|
|
357
|
+
return this.loader.unloadPlugin(id);
|
|
358
|
+
}
|
|
359
|
+
async reloadPlugin(id) {
|
|
360
|
+
return this.loader.reloadPlugin(id);
|
|
361
|
+
}
|
|
362
|
+
async loadFromFunction(setup) {
|
|
363
|
+
return this.loader.loadFromFunction(setup);
|
|
364
|
+
}
|
|
365
|
+
async scanAndLoad() {
|
|
366
|
+
const cwd = this.options.cwd || process.cwd();
|
|
367
|
+
const globalDir = this.options.globalDir || resolve2(homedir(), ".xbrowser/plugins");
|
|
368
|
+
ensurePluginDependencies(globalDir);
|
|
369
|
+
const dirs = [
|
|
370
|
+
resolve2(cwd, ".xcli/plugins"),
|
|
371
|
+
resolve2(cwd, "../.xcli/plugins"),
|
|
372
|
+
this.options.userDir || resolve2(homedir(), ".xcli/plugins"),
|
|
373
|
+
globalDir
|
|
374
|
+
];
|
|
375
|
+
const loaded = [];
|
|
376
|
+
const seen = /* @__PURE__ */ new Set();
|
|
377
|
+
for (const dir of dirs) {
|
|
378
|
+
if (!existsSync3(dir)) continue;
|
|
379
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
380
|
+
for (const entry of entries) {
|
|
381
|
+
if (!entry.isDirectory()) continue;
|
|
382
|
+
if (seen.has(entry.name)) continue;
|
|
383
|
+
seen.add(entry.name);
|
|
384
|
+
const pluginDir = resolve2(dir, entry.name);
|
|
385
|
+
let indexPath = resolve2(pluginDir, "index.js");
|
|
386
|
+
if (!existsSync3(indexPath)) {
|
|
387
|
+
indexPath = resolve2(pluginDir, "index.ts");
|
|
388
|
+
}
|
|
389
|
+
if (!existsSync3(indexPath)) continue;
|
|
390
|
+
try {
|
|
391
|
+
if (!existsSync3(resolve2(pluginDir, "package.json"))) {
|
|
392
|
+
console.warn(`\u26A0\uFE0F Plugin "${entry.name}" has no package.json. Use "xbrowser create ${entry.name} --template static" for proper structure.`);
|
|
393
|
+
} else {
|
|
394
|
+
const metadata = PluginMetadataParser.parseFromPackageJson(pluginDir);
|
|
395
|
+
if (!metadata) {
|
|
396
|
+
console.warn(`\u26A0\uFE0F Plugin "${entry.name}" has package.json but no xbrowser metadata. Add { "xbrowser": { "description": "..." } } to package.json.`);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
const instance = await this.loadPlugin(indexPath, entry.name);
|
|
400
|
+
loaded.push(instance);
|
|
401
|
+
} catch (err) {
|
|
402
|
+
if (process.env.XBROWSER_DEBUG) {
|
|
403
|
+
console.warn(`\u26A0\uFE0F Plugin "${entry.name}" load failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
return loaded;
|
|
409
|
+
}
|
|
410
|
+
async unload() {
|
|
411
|
+
return this.loader.unload();
|
|
412
|
+
}
|
|
413
|
+
};
|
|
414
|
+
|
|
415
|
+
// src/utils/plugin-singleton.ts
|
|
416
|
+
var pluginLoader = null;
|
|
417
|
+
var pluginsScanned = false;
|
|
418
|
+
async function getPluginLoader() {
|
|
419
|
+
if (!pluginLoader) {
|
|
420
|
+
pluginLoader = new XBrowserPluginLoader();
|
|
421
|
+
}
|
|
422
|
+
if (!pluginsScanned) {
|
|
423
|
+
await pluginLoader.scanAndLoad();
|
|
424
|
+
pluginsScanned = true;
|
|
425
|
+
}
|
|
426
|
+
return pluginLoader;
|
|
427
|
+
}
|
|
428
|
+
function resetPluginLoader() {
|
|
429
|
+
pluginLoader = null;
|
|
430
|
+
pluginsScanned = false;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
export {
|
|
434
|
+
getPluginLoader,
|
|
435
|
+
resetPluginLoader
|
|
436
|
+
};
|
package/dist/cli.js
CHANGED
|
@@ -54,7 +54,7 @@ import {
|
|
|
54
54
|
killAllDaemonProcesses,
|
|
55
55
|
startDaemonProcess,
|
|
56
56
|
stopDaemonProcess
|
|
57
|
-
} from "./chunk-
|
|
57
|
+
} from "./chunk-JPSFUFPG.js";
|
|
58
58
|
import {
|
|
59
59
|
errMsg
|
|
60
60
|
} from "./chunk-GDKLH7ZY.js";
|
|
@@ -949,10 +949,10 @@ var setCookieCommand = registerCommand({
|
|
|
949
949
|
description: "Set a cookie",
|
|
950
950
|
scope: "page",
|
|
951
951
|
parameters: z8.object({
|
|
952
|
-
name: z8.string(),
|
|
953
|
-
value: z8.string(),
|
|
954
|
-
domain: z8.string().optional(),
|
|
955
|
-
path: z8.string().optional(),
|
|
952
|
+
name: z8.coerce.string(),
|
|
953
|
+
value: z8.coerce.string(),
|
|
954
|
+
domain: z8.coerce.string().optional(),
|
|
955
|
+
path: z8.coerce.string().optional(),
|
|
956
956
|
expires: z8.number().optional(),
|
|
957
957
|
httpOnly: z8.boolean().optional(),
|
|
958
958
|
secure: z8.boolean().optional(),
|
|
@@ -2358,44 +2358,45 @@ var scrapeCommand = registerCommand({
|
|
|
2358
2358
|
switch (p.format) {
|
|
2359
2359
|
case "markdown": {
|
|
2360
2360
|
const tablesMd = await page.evaluate(() => {
|
|
2361
|
-
|
|
2362
|
-
"
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2361
|
+
document.querySelectorAll(
|
|
2362
|
+
'.el-table__fixed, .el-table__fixed-right, [class*="fixed-left"], [class*="fixed-right"], .ant-table-fixed-left, .ant-table-fixed-right'
|
|
2363
|
+
).forEach((el) => el.remove());
|
|
2364
|
+
document.querySelectorAll("table").forEach((t) => {
|
|
2365
|
+
if (t.closest(".el-table__fixed, .el-table__fixed-right")) t.remove();
|
|
2366
|
+
});
|
|
2367
|
+
const tables = document.querySelectorAll("table");
|
|
2368
|
+
if (tables.length === 0) {
|
|
2369
|
+
const altTables = document.querySelectorAll(
|
|
2370
|
+
'[role="table"], [role="grid"], .el-table__body, .ant-table-tbody'
|
|
2371
|
+
);
|
|
2372
|
+
if (altTables.length === 0) return "";
|
|
2373
|
+
return Array.from(altTables).map((table) => {
|
|
2374
|
+
return extractRowsFromContainer(table);
|
|
2375
|
+
}).filter((md) => md).join("\n\n");
|
|
2376
|
+
}
|
|
2376
2377
|
return Array.from(tables).map((table) => {
|
|
2377
|
-
|
|
2378
|
+
return extractRowsFromContainer(table);
|
|
2379
|
+
}).filter((md) => md).join("\n\n");
|
|
2380
|
+
function extractRowsFromContainer(container) {
|
|
2381
|
+
const rows = container.querySelectorAll(':scope > tr, :scope > thead > tr, :scope > tbody > tr, :scope > tfoot > tr, [role="row"]');
|
|
2378
2382
|
if (rows.length === 0) return "";
|
|
2379
2383
|
const mdRows = Array.from(rows).map((row) => {
|
|
2380
|
-
const cells = row.querySelectorAll('th, td, [role="columnheader"], [role="cell"]
|
|
2384
|
+
const cells = row.querySelectorAll(':scope > th, :scope > td, :scope > [role="columnheader"], :scope > [role="cell"]');
|
|
2385
|
+
if (cells.length === 0) return "";
|
|
2381
2386
|
return "| " + Array.from(cells).map((c) => {
|
|
2382
2387
|
const cellText = c.innerText?.trim().replace(/\n/g, " ") || "";
|
|
2383
2388
|
return cellText.replace(/\|/g, "\\|") || "";
|
|
2384
2389
|
}).join(" | ") + " |";
|
|
2385
|
-
}).
|
|
2390
|
+
}).filter((r) => r);
|
|
2391
|
+
if (mdRows.length === 0) return "";
|
|
2386
2392
|
const headerRow = rows[0];
|
|
2387
|
-
const headerCells = headerRow.querySelectorAll('th, [role="columnheader"]
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
const sep = "| " + Array(headerCount).fill("---").join(" | ") + " |";
|
|
2392
|
-
return mdRows.split("\n").map((line, i) => {
|
|
2393
|
-
if (i === 0) return line + "\n" + sep;
|
|
2394
|
-
return line;
|
|
2395
|
-
}).join("\n");
|
|
2393
|
+
const headerCells = headerRow.querySelectorAll(':scope > th, :scope > [role="columnheader"]');
|
|
2394
|
+
if (headerCells.length > 0) {
|
|
2395
|
+
const sep = "| " + Array(headerCells.length).fill("---").join(" | ") + " |";
|
|
2396
|
+
return mdRows[0] + "\n" + sep + "\n" + mdRows.slice(1).join("\n");
|
|
2396
2397
|
}
|
|
2397
|
-
return mdRows;
|
|
2398
|
-
}
|
|
2398
|
+
return mdRows.join("\n");
|
|
2399
|
+
}
|
|
2399
2400
|
});
|
|
2400
2401
|
content = htmlToMarkdown(html, { onlyMainContent: p.onlyMainContent });
|
|
2401
2402
|
if (tablesMd) {
|
|
@@ -7047,7 +7048,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
7047
7048
|
params = result.data;
|
|
7048
7049
|
}
|
|
7049
7050
|
if (command.scope !== "cli" && !process.env.XBROWSER_DAEMON_WORKER) {
|
|
7050
|
-
const { forwardExec } = await import("./daemon-client-
|
|
7051
|
+
const { forwardExec } = await import("./daemon-client-XXKMJZZ7.js");
|
|
7051
7052
|
const result = await forwardExec(commandName, params, sessionName, extraOpts?.cdpEndpoint);
|
|
7052
7053
|
if (result) return result;
|
|
7053
7054
|
}
|
|
@@ -10271,6 +10272,18 @@ async function handlePlugin(args, options, mode) {
|
|
|
10271
10272
|
await (await getPluginLoader()).reloadPlugin(result.name);
|
|
10272
10273
|
} catch {
|
|
10273
10274
|
}
|
|
10275
|
+
try {
|
|
10276
|
+
const { daemonPing } = await import("./daemon-client-XXKMJZZ7.js");
|
|
10277
|
+
if (await daemonPing()) {
|
|
10278
|
+
await fetch("http://localhost:9224/rpc", {
|
|
10279
|
+
method: "POST",
|
|
10280
|
+
headers: { "Content-Type": "application/json" },
|
|
10281
|
+
body: JSON.stringify({ method: "plugins:reload", params: {} }),
|
|
10282
|
+
signal: AbortSignal.timeout(5e3)
|
|
10283
|
+
});
|
|
10284
|
+
}
|
|
10285
|
+
} catch {
|
|
10286
|
+
}
|
|
10274
10287
|
outputResult(
|
|
10275
10288
|
{ ok: true, name: result.name, source: result.source, path: result.path },
|
|
10276
10289
|
mode
|
|
@@ -10426,7 +10439,8 @@ async function handleRecord(args, options, mode) {
|
|
|
10426
10439
|
}
|
|
10427
10440
|
case "stop": {
|
|
10428
10441
|
const sessionName = options.session || "default";
|
|
10429
|
-
const
|
|
10442
|
+
const output = options.output || options.o;
|
|
10443
|
+
const result = await forwardRecordStop(sessionName, output);
|
|
10430
10444
|
if (!result.ok) {
|
|
10431
10445
|
outputError(String(result.error || "Failed to stop recording"));
|
|
10432
10446
|
return;
|
|
@@ -10435,6 +10449,7 @@ async function handleRecord(args, options, mode) {
|
|
|
10435
10449
|
ok: true,
|
|
10436
10450
|
message: "Recording stopped.",
|
|
10437
10451
|
sessionName,
|
|
10452
|
+
output: result.output || (output || SessionRecorder.getRecordingsDir(sessionName) + "/recording.json"),
|
|
10438
10453
|
actions: result.actions,
|
|
10439
10454
|
network: result.network,
|
|
10440
10455
|
durationMs: result.durationMs,
|
|
@@ -12548,7 +12563,7 @@ Run "xbrowser ${command} ${subCommand} --help" to see available parameters.`
|
|
|
12548
12563
|
}
|
|
12549
12564
|
const needsBrowser = cmdEntry.scope === "page" || cmdEntry.scope === "browser";
|
|
12550
12565
|
if (needsBrowser && !process.env.XBROWSER_DAEMON_WORKER) {
|
|
12551
|
-
const { forwardExec } = await import("./daemon-client-
|
|
12566
|
+
const { forwardExec } = await import("./daemon-client-XXKMJZZ7.js");
|
|
12552
12567
|
const userTimeout = typeof params.timeout === "number" && params.timeout > 0 ? params.timeout * 1e3 + 3e4 : void 0;
|
|
12553
12568
|
const result = await forwardExec(`${command}.${subCommand}`, params, sessionName, cdpEndpoint, userTimeout);
|
|
12554
12569
|
const resultData = result && typeof result === "object" && "data" in result ? result.data : void 0;
|
|
@@ -162,8 +162,10 @@ async function forwardNetworkInspect(sessionName, id) {
|
|
|
162
162
|
async function forwardRecordStart(session, url, cdpEndpoint) {
|
|
163
163
|
return rpcCall("record:start", { session, url, cdpEndpoint }, 15e3);
|
|
164
164
|
}
|
|
165
|
-
async function forwardRecordStop(session) {
|
|
166
|
-
|
|
165
|
+
async function forwardRecordStop(session, output) {
|
|
166
|
+
const params = { session };
|
|
167
|
+
if (output) params.output = output;
|
|
168
|
+
return rpcCall("record:stop", params, 1e4);
|
|
167
169
|
}
|
|
168
170
|
async function forwardRecordStatus(session) {
|
|
169
171
|
return rpcCall("record:status", { session }, 5e3);
|