@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.
@@ -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
- return rpcCall("record:stop", { session }, 1e4);
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-XYXCS7JW.js";
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
- const tableSelectors = [
2362
- "table",
2363
- '[role="table"]',
2364
- '[role="grid"]',
2365
- '[class*="el-table"]',
2366
- // Element UI
2367
- '[class*="ant-table"]',
2368
- // Ant Design
2369
- '[class*="MuiTable"]',
2370
- // Material UI
2371
- '[class*="table"]'
2372
- // Generic table-like
2373
- ].join(",");
2374
- const tables = document.querySelectorAll(tableSelectors);
2375
- if (tables.length === 0) return "";
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
- const rows = table.querySelectorAll('tr, [role="row"], [class*="row"]');
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"], [class*="cell"], [class*="col"]');
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
- }).join("\n");
2390
+ }).filter((r) => r);
2391
+ if (mdRows.length === 0) return "";
2386
2392
  const headerRow = rows[0];
2387
- const headerCells = headerRow.querySelectorAll('th, [role="columnheader"], [class*="header"]');
2388
- const hasHeader = headerCells.length > 0;
2389
- if (hasHeader && mdRows) {
2390
- const headerCount = headerCells.length;
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
- }).join("\n\n");
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-ZHO6NG36.js");
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 result = await forwardRecordStop(sessionName);
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-ZHO6NG36.js");
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
- return rpcCall("record:stop", { session }, 1e4);
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);
@@ -29,7 +29,7 @@ import {
29
29
  forwardSessionList,
30
30
  forwardViewerCheckSelector,
31
31
  isDaemonRunning
32
- } from "./chunk-XYXCS7JW.js";
32
+ } from "./chunk-JPSFUFPG.js";
33
33
  import "./chunk-GDKLH7ZY.js";
34
34
  import "./chunk-KFQGP6VL.js";
35
35
  export {