@xbrowser/cli 1.2.1 → 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.
@@ -24,6 +24,9 @@ import {
24
24
  } from "./chunk-SLQR57XZ.js";
25
25
  import "./chunk-QFROODUU.js";
26
26
  import "./chunk-TNEN6VQ2.js";
27
+ import {
28
+ getPluginLoader
29
+ } from "./chunk-PHBK3TRN.js";
27
30
  import {
28
31
  getDaemonConfig,
29
32
  getDaemonProcessStatus
@@ -34,15 +37,15 @@ import {
34
37
  import "./chunk-KFQGP6VL.js";
35
38
 
36
39
  // src/daemon/daemon-main.ts
37
- import { writeFileSync as writeFileSync8, mkdirSync as mkdirSync7, appendFileSync, unlinkSync } from "fs";
38
- import { join as join9 } from "path";
39
- import { homedir as homedir9 } from "os";
40
+ import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync6, appendFileSync, unlinkSync } from "fs";
41
+ import { join as join8 } from "path";
42
+ import { homedir as homedir8 } from "os";
40
43
  import { startHttpServer } from "@dyyz1993/xcli-core";
41
44
 
42
45
  // src/daemon/rpc-handlers.ts
43
- import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, readFileSync as readFileSync6 } from "fs";
44
- import { join as join8 } from "path";
45
- import { homedir as homedir8 } from "os";
46
+ import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, readFileSync as readFileSync5 } from "fs";
47
+ import { join as join7 } from "path";
48
+ import { homedir as homedir7 } from "os";
46
49
  import {
47
50
  createSessionMeta,
48
51
  removeSession
@@ -449,15 +452,15 @@ var clickCommand = registerCommand({
449
452
  let detectedNewPage;
450
453
  let cleanup;
451
454
  if (ctx.browserContext?.on) {
452
- const pagePromise = new Promise((resolve3) => {
455
+ const pagePromise = new Promise((resolve) => {
453
456
  const timer = setTimeout(() => {
454
457
  ctx.browserContext.off("page", handler);
455
- resolve3(void 0);
458
+ resolve(void 0);
456
459
  }, 3e3);
457
460
  const handler = (page2) => {
458
461
  clearTimeout(timer);
459
462
  ctx.browserContext.off("page", handler);
460
- resolve3(page2);
463
+ resolve(page2);
461
464
  };
462
465
  ctx.browserContext.on("page", handler);
463
466
  });
@@ -907,10 +910,10 @@ var setCookieCommand = registerCommand({
907
910
  description: "Set a cookie",
908
911
  scope: "page",
909
912
  parameters: z8.object({
910
- name: z8.string(),
911
- value: z8.string(),
912
- domain: z8.string().optional(),
913
- path: z8.string().optional(),
913
+ name: z8.coerce.string(),
914
+ value: z8.coerce.string(),
915
+ domain: z8.coerce.string().optional(),
916
+ path: z8.coerce.string().optional(),
914
917
  expires: z8.number().optional(),
915
918
  httpOnly: z8.boolean().optional(),
916
919
  secure: z8.boolean().optional(),
@@ -1255,7 +1258,7 @@ var consoleCheckCommand = registerCommand({
1255
1258
  await page.goto(p.url, { waitUntil: "domcontentloaded" });
1256
1259
  }
1257
1260
  const messages = await page.evaluate((args) => {
1258
- return new Promise((resolve3) => {
1261
+ return new Promise((resolve) => {
1259
1262
  const collected = [];
1260
1263
  const originalConsole = {
1261
1264
  log: console.log,
@@ -1322,7 +1325,7 @@ ${a.stack || ""}`;
1322
1325
  console.warn = originalConsole.warn;
1323
1326
  console.error = originalConsole.error;
1324
1327
  console.info = originalConsole.info;
1325
- resolve3(collected);
1328
+ resolve(collected);
1326
1329
  }, args.duration);
1327
1330
  });
1328
1331
  }, { duration: p.duration });
@@ -1748,7 +1751,7 @@ async function executeAction(page, action) {
1748
1751
  if (action.selector) {
1749
1752
  await page.waitForSelector(action.selector, { timeout: 3e4 });
1750
1753
  } else if (action.milliseconds) {
1751
- await new Promise((resolve3) => setTimeout(resolve3, action.milliseconds));
1754
+ await new Promise((resolve) => setTimeout(resolve, action.milliseconds));
1752
1755
  } else {
1753
1756
  throw new Error("wait action requires either milliseconds or selector");
1754
1757
  }
@@ -1837,8 +1840,8 @@ var actionsCommand = registerCommand({
1837
1840
  results.push(result);
1838
1841
  }
1839
1842
  })();
1840
- const timeoutPromise = new Promise((resolve3) => {
1841
- setTimeout(resolve3, timeoutMs);
1843
+ const timeoutPromise = new Promise((resolve) => {
1844
+ setTimeout(resolve, timeoutMs);
1842
1845
  });
1843
1846
  await Promise.race([executionPromise, timeoutPromise]);
1844
1847
  const title = await ctx.page.title();
@@ -2558,11 +2561,11 @@ async function navigateForMap(page, url, timeout = 15e3) {
2558
2561
  }
2559
2562
  async function extractPageLinks(page, baseUrl) {
2560
2563
  await navigateForMap(page, baseUrl);
2561
- await new Promise((resolve3) => setTimeout(resolve3, 2e3));
2564
+ await new Promise((resolve) => setTimeout(resolve, 2e3));
2562
2565
  await page.evaluate(() => {
2563
2566
  window.scrollTo(0, document.body.scrollHeight);
2564
2567
  });
2565
- await new Promise((resolve3) => setTimeout(resolve3, 1e3));
2568
+ await new Promise((resolve) => setTimeout(resolve, 1e3));
2566
2569
  const origin = new URL(baseUrl).origin;
2567
2570
  const rawLinks = await page.evaluate((evalOrigin) => {
2568
2571
  return Array.from(document.querySelectorAll("a[href]")).map((a) => {
@@ -4420,25 +4423,25 @@ aria snapshot\uFF1A
4420
4423
  async function analyzeWithLLM(ariaSnapshot) {
4421
4424
  const piBin = process.env.PI_CLI_PATH || "pi";
4422
4425
  const prompt = LLM_PROMPT.replace("{snapshot}", ariaSnapshot.slice(0, 4e3));
4423
- return new Promise((resolve3) => {
4426
+ return new Promise((resolve) => {
4424
4427
  execFile(
4425
4428
  piBin,
4426
4429
  ["--provider", LLM_PROVIDER, "--model", LLM_MODEL, prompt],
4427
4430
  { timeout: LLM_TIMEOUT_MS, maxBuffer: 1024 * 1024 },
4428
4431
  (err, stdout, _stderr) => {
4429
4432
  if (err) {
4430
- resolve3(null);
4433
+ resolve(null);
4431
4434
  return;
4432
4435
  }
4433
4436
  const output = (stdout || "").trim();
4434
4437
  if (!output) {
4435
- resolve3(null);
4438
+ resolve(null);
4436
4439
  return;
4437
4440
  }
4438
4441
  try {
4439
4442
  const parsed = parse(output);
4440
4443
  if (!parsed || typeof parsed !== "object") {
4441
- resolve3(null);
4444
+ resolve(null);
4442
4445
  return;
4443
4446
  }
4444
4447
  const elements = {};
@@ -4453,9 +4456,9 @@ async function analyzeWithLLM(ariaSnapshot) {
4453
4456
  };
4454
4457
  }
4455
4458
  }
4456
- resolve3(Object.keys(elements).length > 0 ? elements : null);
4459
+ resolve(Object.keys(elements).length > 0 ? elements : null);
4457
4460
  } catch {
4458
- resolve3(null);
4461
+ resolve(null);
4459
4462
  }
4460
4463
  }
4461
4464
  );
@@ -4810,7 +4813,7 @@ async function pollUntil(timeout, pollInterval, predicate) {
4810
4813
  const startedAt = Date.now();
4811
4814
  while (Date.now() - startedAt <= timeout) {
4812
4815
  if (await predicate()) return true;
4813
- await new Promise((resolve3) => setTimeout(resolve3, pollInterval));
4816
+ await new Promise((resolve) => setTimeout(resolve, pollInterval));
4814
4817
  }
4815
4818
  return false;
4816
4819
  }
@@ -5416,11 +5419,11 @@ function resolveScriptContent(params) {
5416
5419
  async function readStdin() {
5417
5420
  const { createReadStream } = await import("fs");
5418
5421
  const { createInterface } = await import("readline");
5419
- return new Promise((resolve3, reject) => {
5422
+ return new Promise((resolve, reject) => {
5420
5423
  const lines = [];
5421
5424
  const rl = createInterface({ input: createReadStream("/dev/stdin") });
5422
5425
  rl.on("line", (line) => lines.push(line));
5423
- rl.on("close", () => resolve3(lines.join("\n")));
5426
+ rl.on("close", () => resolve(lines.join("\n")));
5424
5427
  rl.on("error", reject);
5425
5428
  });
5426
5429
  }
@@ -5888,434 +5891,6 @@ function formatDetectionMessage(result) {
5888
5891
  Action: ${action}`;
5889
5892
  }
5890
5893
 
5891
- // src/plugin/loader.ts
5892
- import {
5893
- Core
5894
- } from "@dyyz1993/xcli-core";
5895
- import { resolve as resolve2 } from "path";
5896
- import { existsSync as existsSync4, readdirSync } from "fs";
5897
- import { homedir as homedir5 } from "os";
5898
-
5899
- // src/plugin/metadata-parser.ts
5900
- import { existsSync as existsSync2 } from "fs";
5901
- import { resolve } from "path";
5902
-
5903
- // src/utils/json-file.ts
5904
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync4 } from "fs";
5905
- function readJsonFile(filePath, defaultValue) {
5906
- try {
5907
- const content = readFileSync3(filePath, "utf-8");
5908
- return JSON.parse(content);
5909
- } catch {
5910
- return defaultValue;
5911
- }
5912
- }
5913
-
5914
- // src/plugin/metadata-parser.ts
5915
- var PluginMetadataParser = class {
5916
- static XBROWSER_KEYWORDS = ["xbrowser", "xbrowser-plugin"];
5917
- static parseFromPackageJson(pluginPath) {
5918
- const packageJsonPath = resolve(pluginPath, "package.json");
5919
- if (!existsSync2(packageJsonPath)) {
5920
- return null;
5921
- }
5922
- const packageJson = readJsonFile(packageJsonPath, null);
5923
- if (!packageJson) return null;
5924
- if (!packageJson.xbrowser) {
5925
- return null;
5926
- }
5927
- const xbrowser = packageJson.xbrowser;
5928
- const metadata = {
5929
- id: xbrowser.id || packageJson.name,
5930
- name: xbrowser.name || packageJson.name,
5931
- description: xbrowser.description || packageJson.description || "",
5932
- version: xbrowser.version || packageJson.version || "1.0.0",
5933
- author: xbrowser.author || this.extractAuthor(packageJson.author),
5934
- homepage: xbrowser.homepage || packageJson.homepage,
5935
- commands: xbrowser.commands,
5936
- sites: xbrowser.sites,
5937
- tags: xbrowser.tags,
5938
- screenshot: xbrowser.screenshot,
5939
- license: xbrowser.license || packageJson.license
5940
- };
5941
- return metadata;
5942
- }
5943
- static isXBrowserPlugin(packageJson) {
5944
- if (packageJson.xbrowser) {
5945
- return true;
5946
- }
5947
- const keywords = packageJson.keywords;
5948
- if (!keywords) return false;
5949
- return this.XBROWSER_KEYWORDS.some((kw) => keywords.includes(kw));
5950
- }
5951
- static fromNPMResult(result) {
5952
- const author = typeof result.author === "string" ? result.author : result.author?.name || "Unknown";
5953
- return {
5954
- id: result.name,
5955
- name: result.name.replace(/^xbrowser-plugin-/, "").replace(/^@[^/]+\//, ""),
5956
- description: result.description || "",
5957
- version: result.version,
5958
- author,
5959
- homepage: result.homepage || result.links?.homepage,
5960
- tags: result.keywords,
5961
- license: ""
5962
- };
5963
- }
5964
- static extractAuthor(author) {
5965
- if (typeof author === "string") return author;
5966
- if (typeof author === "object" && author !== null) {
5967
- const authorObj = author;
5968
- return authorObj.name || "Unknown";
5969
- }
5970
- return "Unknown";
5971
- }
5972
- static validateMetadata(metadata) {
5973
- const errors = [];
5974
- if (!metadata.id) errors.push("id is required");
5975
- if (!metadata.name) errors.push("name is required");
5976
- if (!metadata.description) errors.push("description is required");
5977
- if (!metadata.version) errors.push("version is required");
5978
- return errors;
5979
- }
5980
- };
5981
-
5982
- // src/plugin/ensure-deps.ts
5983
- import { existsSync as existsSync3, mkdirSync as mkdirSync4, writeFileSync as writeFileSync5 } from "fs";
5984
- import { join as join5 } from "path";
5985
- import { execSync } from "child_process";
5986
- var SHARED_PLUGIN_DEPENDENCIES = {
5987
- "zod": "^3.24.0",
5988
- "@dyyz1993/xcli-core": "^0.12.1"
5989
- };
5990
- function ensurePluginDependencies(pluginsDir) {
5991
- const zodPath = join5(pluginsDir, "node_modules", "zod");
5992
- if (existsSync3(zodPath)) return;
5993
- mkdirSync4(pluginsDir, { recursive: true });
5994
- const pkgPath = join5(pluginsDir, "package.json");
5995
- let pkg = {};
5996
- if (existsSync3(pkgPath)) {
5997
- try {
5998
- pkg = readJsonFile(pkgPath, {});
5999
- } catch {
6000
- }
6001
- }
6002
- const existingDeps = pkg.dependencies || {};
6003
- let needsInstall = false;
6004
- for (const [dep, version] of Object.entries(SHARED_PLUGIN_DEPENDENCIES)) {
6005
- if (!existingDeps[dep]) {
6006
- existingDeps[dep] = version;
6007
- needsInstall = true;
6008
- }
6009
- }
6010
- if (!needsInstall && existsSync3(join5(pluginsDir, "node_modules"))) return;
6011
- pkg.dependencies = existingDeps;
6012
- pkg.private = true;
6013
- pkg.description = pkg.description || "xbrowser plugins \u2014 shared dependencies";
6014
- writeFileSync5(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf-8");
6015
- try {
6016
- execSync("npm install --production --no-package-lock --no-fund --no-audit", {
6017
- cwd: pluginsDir,
6018
- stdio: "pipe",
6019
- timeout: 6e4,
6020
- env: { ...process.env, NODE_ENV: "production" }
6021
- });
6022
- } catch (err) {
6023
- console.warn(`\u26A0\uFE0F Failed to install shared plugin dependencies: ${err instanceof Error ? err.message : String(err)}`);
6024
- }
6025
- }
6026
-
6027
- // src/plugin/contract.ts
6028
- import {
6029
- unwrapZod,
6030
- fieldsFromZodObjectReflected,
6031
- zodTypeToContractType
6032
- } from "@dyyz1993/xcli-core";
6033
- function buildPluginContract(site) {
6034
- const commands = site.getAllCommands().map((command) => buildCommandContract(site.getCommand?.(command.name) || command, {
6035
- siteRequiresLogin: site.config?.requiresLogin
6036
- }));
6037
- return {
6038
- version: 2,
6039
- plugin: {
6040
- name: site.name,
6041
- url: site.url,
6042
- description: site.config?.description,
6043
- requiresLogin: site.config?.requiresLogin
6044
- },
6045
- commands
6046
- };
6047
- }
6048
- function buildCommandContract(command, options = {}) {
6049
- const extension = command.xbrowser || {};
6050
- const inferredFields = fieldsFromZodObject(command.parameters);
6051
- const fields = mergeFields(inferredFields, extension.form?.fields || []);
6052
- const positional = extension.positional || fields.filter((field) => field.positional).map((field) => field.name);
6053
- const requiresLogin = command.requiresLogin === true || options.siteRequiresLogin === true && command.name !== "login" && command.name !== "logout";
6054
- const capabilities = extension.capabilities || inferCapabilities(command.scope || "project", requiresLogin);
6055
- const outputSchema = command.result ? summarizeZod(command.result) : void 0;
6056
- return {
6057
- name: command.name,
6058
- description: command.description || "",
6059
- scope: command.scope || "project",
6060
- requiresLogin,
6061
- category: extension.category,
6062
- capabilities,
6063
- positional,
6064
- form: {
6065
- title: extension.form?.title || command.description || command.name,
6066
- description: extension.form?.description,
6067
- submitLabel: extension.form?.submitLabel || "Run",
6068
- fields
6069
- },
6070
- output: extension.output || (outputSchema ? { schema: outputSchema } : void 0)
6071
- };
6072
- }
6073
- function fieldsFromZodObject(schema) {
6074
- const reflected = fieldsFromZodObjectReflected(schema);
6075
- return reflected.map((field) => {
6076
- const widget = widgetFor(field.type, field.enum);
6077
- return {
6078
- name: field.name,
6079
- label: toLabel(field.name),
6080
- type: field.type,
6081
- widget,
6082
- required: field.required,
6083
- ...field.description ? { description: field.description } : {},
6084
- ...field.default !== void 0 ? { default: field.default } : {},
6085
- ...field.enum ? { enum: field.enum } : {},
6086
- ...field.type === "array" ? { multiple: true } : {}
6087
- };
6088
- });
6089
- }
6090
- function mergeFields(inferred, overrides) {
6091
- if (overrides.length === 0) return inferred;
6092
- const byName = new Map(inferred.map((field) => [field.name, field]));
6093
- const seen = /* @__PURE__ */ new Set();
6094
- const merged = [];
6095
- for (const override of overrides) {
6096
- if (!override.name) continue;
6097
- const base = byName.get(override.name) || {
6098
- name: override.name,
6099
- label: toLabel(override.name),
6100
- type: "string",
6101
- widget: "text",
6102
- required: false
6103
- };
6104
- merged.push({ ...base, ...override, name: override.name });
6105
- seen.add(override.name);
6106
- }
6107
- for (const field of inferred) {
6108
- if (!seen.has(field.name)) merged.push(field);
6109
- }
6110
- return merged;
6111
- }
6112
- function inferCapabilities(scope, requiresLogin) {
6113
- const caps = [];
6114
- if (scope === "page") caps.push("browser.page");
6115
- if (scope === "browser") caps.push("browser.context");
6116
- if (requiresLogin) caps.push("auth.login");
6117
- return caps;
6118
- }
6119
- function widgetFor(type, enumValues) {
6120
- if (enumValues) return "select";
6121
- if (type === "boolean") return "checkbox";
6122
- if (type === "number") return "number";
6123
- if (type === "array") return "multi-select";
6124
- if (type === "object") return "json";
6125
- return "text";
6126
- }
6127
- function summarizeZod(schema) {
6128
- const unwrapped = unwrapZod(schema);
6129
- if (unwrapped.typeName === "ZodArray") {
6130
- const def = unwrapped.schema?._def;
6131
- return {
6132
- type: "array",
6133
- items: summarizeZod(def?.type || def?.innerType)
6134
- };
6135
- }
6136
- const shape = getObjectShape(schema);
6137
- if (!shape) {
6138
- return {
6139
- type: zodTypeToContractType(unwrapped.typeName),
6140
- required: !unwrapped.optional,
6141
- ...unwrapped.description ? { description: unwrapped.description } : {}
6142
- };
6143
- }
6144
- return Object.fromEntries(
6145
- Object.entries(shape).map(([name, field]) => {
6146
- const inner = unwrapZod(field);
6147
- return [name, {
6148
- type: zodTypeToContractType(inner.typeName),
6149
- required: !inner.optional,
6150
- ...inner.description ? { description: inner.description } : {}
6151
- }];
6152
- })
6153
- );
6154
- }
6155
- function getObjectShape(schema) {
6156
- const zod = schema;
6157
- const shapeOrFn = zod?.shape ?? zod?._def?.shape;
6158
- if (!shapeOrFn) return void 0;
6159
- return typeof shapeOrFn === "function" ? shapeOrFn() : shapeOrFn;
6160
- }
6161
- function toLabel(name) {
6162
- return name.replace(/([A-Z])/g, " $1").replace(/[-_]+/g, " ").replace(/\s+/g, " ").trim().replace(/^./, (char) => char.toUpperCase());
6163
- }
6164
-
6165
- // src/plugin/login-required-patch.ts
6166
- import { SiteInstanceImpl } from "@dyyz1993/xcli-core";
6167
- var patched = false;
6168
- function patchLoginRequired() {
6169
- if (patched) return;
6170
- patched = true;
6171
- const target = SiteInstanceImpl.prototype;
6172
- const originalCommand = target.command;
6173
- const wrapped = function(...args) {
6174
- const result = originalCommand.apply(this, args);
6175
- const [name, cmd] = args;
6176
- const loginRequired = cmd.loginRequired;
6177
- if (loginRequired) {
6178
- const commands = this.commands;
6179
- const entry = commands?.get(name);
6180
- if (entry) {
6181
- entry.loginRequired = loginRequired;
6182
- }
6183
- }
6184
- return result;
6185
- };
6186
- Object.defineProperty(target, "command", {
6187
- value: wrapped,
6188
- writable: true,
6189
- configurable: true
6190
- });
6191
- }
6192
-
6193
- // src/plugin/loader.ts
6194
- var DEFAULT_PLUGIN_DIRS = [".xcli/plugins", "../.xcli/plugins"];
6195
- var XBrowserPluginLoader = class {
6196
- core;
6197
- loader;
6198
- options;
6199
- constructor(options) {
6200
- patchLoginRequired();
6201
- this.options = options ?? {};
6202
- const cwd = this.options.cwd || process.cwd();
6203
- const coreConfig = {
6204
- name: "xbrowser",
6205
- version: "0.1.0",
6206
- description: "Browser automation CLI",
6207
- configDirName: ".xbrowser",
6208
- envPrefix: "XBROWSER",
6209
- pluginDirs: [
6210
- ...DEFAULT_PLUGIN_DIRS,
6211
- resolve2(cwd, ".xcli/plugins")
6212
- ]
6213
- };
6214
- this.core = new Core(coreConfig);
6215
- this.loader = this.core.loader;
6216
- }
6217
- getAPI() {
6218
- return this.loader.getAPI();
6219
- }
6220
- /**
6221
- * Get the core instance for external use.
6222
- * @returns The xcli-core Core instance.
6223
- */
6224
- getCore() {
6225
- return this.core;
6226
- }
6227
- getPlugin(id) {
6228
- return this.loader.getPlugin(id);
6229
- }
6230
- getPluginStatus(id) {
6231
- return this.loader.getPluginStatus(id);
6232
- }
6233
- getLoadedPlugins() {
6234
- return this.loader.getLoadedPlugins();
6235
- }
6236
- getPluginContract(siteName, commandName) {
6237
- const site = this.core.loader.getSite(siteName);
6238
- if (!site) return void 0;
6239
- const contract = buildPluginContract(site);
6240
- if (!commandName) return contract;
6241
- return contract.commands.find((command) => command.name === commandName);
6242
- }
6243
- async loadPlugin(pluginPath, id) {
6244
- return this.loader.loadPlugin(pluginPath, id);
6245
- }
6246
- async unloadPlugin(id) {
6247
- return this.loader.unloadPlugin(id);
6248
- }
6249
- async reloadPlugin(id) {
6250
- return this.loader.reloadPlugin(id);
6251
- }
6252
- async loadFromFunction(setup) {
6253
- return this.loader.loadFromFunction(setup);
6254
- }
6255
- async scanAndLoad() {
6256
- const cwd = this.options.cwd || process.cwd();
6257
- const globalDir = this.options.globalDir || resolve2(homedir5(), ".xbrowser/plugins");
6258
- ensurePluginDependencies(globalDir);
6259
- const dirs = [
6260
- resolve2(cwd, ".xcli/plugins"),
6261
- resolve2(cwd, "../.xcli/plugins"),
6262
- this.options.userDir || resolve2(homedir5(), ".xcli/plugins"),
6263
- globalDir
6264
- ];
6265
- const loaded = [];
6266
- const seen = /* @__PURE__ */ new Set();
6267
- for (const dir of dirs) {
6268
- if (!existsSync4(dir)) continue;
6269
- const entries = readdirSync(dir, { withFileTypes: true });
6270
- for (const entry of entries) {
6271
- if (!entry.isDirectory()) continue;
6272
- if (seen.has(entry.name)) continue;
6273
- seen.add(entry.name);
6274
- const pluginDir = resolve2(dir, entry.name);
6275
- let indexPath = resolve2(pluginDir, "index.js");
6276
- if (!existsSync4(indexPath)) {
6277
- indexPath = resolve2(pluginDir, "index.ts");
6278
- }
6279
- if (!existsSync4(indexPath)) continue;
6280
- try {
6281
- if (!existsSync4(resolve2(pluginDir, "package.json"))) {
6282
- console.warn(`\u26A0\uFE0F Plugin "${entry.name}" has no package.json. Use "xbrowser create ${entry.name} --template static" for proper structure.`);
6283
- } else {
6284
- const metadata = PluginMetadataParser.parseFromPackageJson(pluginDir);
6285
- if (!metadata) {
6286
- console.warn(`\u26A0\uFE0F Plugin "${entry.name}" has package.json but no xbrowser metadata. Add { "xbrowser": { "description": "..." } } to package.json.`);
6287
- }
6288
- }
6289
- const instance = await this.loadPlugin(indexPath, entry.name);
6290
- loaded.push(instance);
6291
- } catch (err) {
6292
- if (process.env.XBROWSER_DEBUG) {
6293
- console.warn(`\u26A0\uFE0F Plugin "${entry.name}" load failed: ${err instanceof Error ? err.message : String(err)}`);
6294
- }
6295
- }
6296
- }
6297
- }
6298
- return loaded;
6299
- }
6300
- async unload() {
6301
- return this.loader.unload();
6302
- }
6303
- };
6304
-
6305
- // src/utils/plugin-singleton.ts
6306
- var pluginLoader = null;
6307
- var pluginsScanned = false;
6308
- async function getPluginLoader() {
6309
- if (!pluginLoader) {
6310
- pluginLoader = new XBrowserPluginLoader();
6311
- }
6312
- if (!pluginsScanned) {
6313
- await pluginLoader.scanAndLoad();
6314
- pluginsScanned = true;
6315
- }
6316
- return pluginLoader;
6317
- }
6318
-
6319
5894
  // src/utils/viewer-url.ts
6320
5895
  function buildViewerUrl(sessionName = "default") {
6321
5896
  try {
@@ -6799,7 +6374,7 @@ var TipsManager = class {
6799
6374
  }
6800
6375
  }
6801
6376
  debounce() {
6802
- return new Promise((resolve3) => setTimeout(resolve3, DEBOUNCE_MS));
6377
+ return new Promise((resolve) => setTimeout(resolve, DEBOUNCE_MS));
6803
6378
  }
6804
6379
  formatTips(tips) {
6805
6380
  return tips.map((tip) => {
@@ -6916,11 +6491,11 @@ async function loadHooks() {
6916
6491
  }
6917
6492
 
6918
6493
  // src/executor.ts
6919
- import { homedir as homedir6 } from "os";
6920
- import { join as join6 } from "path";
6494
+ import { homedir as homedir5 } from "os";
6495
+ import { join as join5 } from "path";
6921
6496
  var NAVIGATION_COMMANDS = /* @__PURE__ */ new Set(["goto", "back", "forward", "refresh"]);
6922
6497
  var snapshotHintShown = /* @__PURE__ */ new WeakSet();
6923
- var CONFIG_DIR2 = join6(homedir6(), ".xbrowser");
6498
+ var CONFIG_DIR2 = join5(homedir5(), ".xbrowser");
6924
6499
  var storageCache = /* @__PURE__ */ new Map();
6925
6500
  function getPluginStorage(pluginName) {
6926
6501
  if (!storageCache.has(pluginName)) {
@@ -6932,7 +6507,7 @@ var archiveInitialized = false;
6932
6507
  function ensureArchiveInit() {
6933
6508
  if (!archiveInitialized) {
6934
6509
  try {
6935
- configureArchiveStore({ archiveDir: join6(homedir6(), ".xbrowser", "archives") });
6510
+ configureArchiveStore({ archiveDir: join5(homedir5(), ".xbrowser", "archives") });
6936
6511
  } catch {
6937
6512
  }
6938
6513
  archiveInitialized = true;
@@ -7006,7 +6581,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
7006
6581
  params = result.data;
7007
6582
  }
7008
6583
  if (command.scope !== "cli" && !process.env.XBROWSER_DAEMON_WORKER) {
7009
- const { forwardExec } = await import("./daemon-client-R4QWHD7V.js");
6584
+ const { forwardExec } = await import("./daemon-client-COJQESU2.js");
7010
6585
  const result = await forwardExec(commandName, params, sessionName, extraOpts?.cdpEndpoint);
7011
6586
  if (result) return result;
7012
6587
  }
@@ -7689,10 +7264,10 @@ async function replayEntry(entry, options = {}) {
7689
7264
  }
7690
7265
 
7691
7266
  // src/daemon/feedback-store.ts
7692
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync6, mkdirSync as mkdirSync5 } from "fs";
7693
- import { join as join7 } from "path";
7694
- import { homedir as homedir7 } from "os";
7695
- var FEEDBACK_FILE = join7(homedir7(), ".xbrowser", "feedback.json");
7267
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4 } from "fs";
7268
+ import { join as join6 } from "path";
7269
+ import { homedir as homedir6 } from "os";
7270
+ var FEEDBACK_FILE = join6(homedir6(), ".xbrowser", "feedback.json");
7696
7271
  var FeedbackStore = class {
7697
7272
  entries = [];
7698
7273
  constructor() {
@@ -7700,7 +7275,7 @@ var FeedbackStore = class {
7700
7275
  }
7701
7276
  load() {
7702
7277
  try {
7703
- const data = readFileSync4(FEEDBACK_FILE, "utf8");
7278
+ const data = readFileSync3(FEEDBACK_FILE, "utf8");
7704
7279
  this.entries = JSON.parse(data);
7705
7280
  } catch {
7706
7281
  this.entries = [];
@@ -7708,8 +7283,8 @@ var FeedbackStore = class {
7708
7283
  }
7709
7284
  save() {
7710
7285
  try {
7711
- mkdirSync5(join7(homedir7(), ".xbrowser"), { recursive: true });
7712
- writeFileSync6(FEEDBACK_FILE, JSON.stringify(this.entries, null, 2));
7286
+ mkdirSync4(join6(homedir6(), ".xbrowser"), { recursive: true });
7287
+ writeFileSync4(FEEDBACK_FILE, JSON.stringify(this.entries, null, 2));
7713
7288
  } catch {
7714
7289
  }
7715
7290
  }
@@ -8007,7 +7582,7 @@ var PlaybackEngine = class _PlaybackEngine {
8007
7582
  // src/daemon/rpc-handlers.ts
8008
7583
  var activeRecorders = /* @__PURE__ */ new Map();
8009
7584
  var replayResumeResolvers = /* @__PURE__ */ new Map();
8010
- var CONFIG_DIR3 = join8(homedir8(), ".xbrowser");
7585
+ var CONFIG_DIR3 = join7(homedir7(), ".xbrowser");
8011
7586
  var RECORDING_INJECT_JS = `
8012
7587
  (function(){
8013
7588
  if(window.__xb_rec) return;
@@ -8099,6 +7674,8 @@ function createRPCHandler() {
8099
7674
  // ── Utility ──
8100
7675
  case "ping":
8101
7676
  return { ok: true, pid: process.pid };
7677
+ case "plugins:reload":
7678
+ return handlePluginsReload();
8102
7679
  // ── Network analysis ──
8103
7680
  case "network:list":
8104
7681
  return handleNetworkList(params);
@@ -8285,6 +7862,13 @@ function createRPCHandler() {
8285
7862
  registerSessionIfNew(sessionName);
8286
7863
  return result;
8287
7864
  }
7865
+ async function handlePluginsReload() {
7866
+ const { resetPluginLoader } = await import("./plugin-singleton-ZBVTWEYK.js");
7867
+ resetPluginLoader();
7868
+ const loader = await import("./plugin-singleton-ZBVTWEYK.js").then((m) => m.getPluginLoader());
7869
+ const sites = loader.getCore().loader.getSites();
7870
+ return { ok: true, plugins: sites.length };
7871
+ }
8288
7872
  async function handleAgentObserve(params) {
8289
7873
  const sessionName = params.session || "default";
8290
7874
  const commandParams = {
@@ -8455,10 +8039,10 @@ function createRPCHandler() {
8455
8039
  if (!sess) return { ok: false, error: "No session" };
8456
8040
  try {
8457
8041
  const events = await sess.page.evaluate(() => window.__xb_evts || []);
8458
- const recordingsDir = join8(CONFIG_DIR3, "recordings");
8459
- mkdirSync6(recordingsDir, { recursive: true });
8460
- const outPath = params.path || join8(recordingsDir, `recording-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.json`);
8461
- writeFileSync7(outPath, JSON.stringify({
8042
+ const recordingsDir = join7(CONFIG_DIR3, "recordings");
8043
+ mkdirSync5(recordingsDir, { recursive: true });
8044
+ const outPath = params.path || join7(recordingsDir, `recording-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.json`);
8045
+ writeFileSync5(outPath, JSON.stringify({
8462
8046
  startUrl: sess.page.url(),
8463
8047
  recordedAt: (/* @__PURE__ */ new Date()).toISOString(),
8464
8048
  events
@@ -8558,13 +8142,19 @@ function createRPCHandler() {
8558
8142
  }
8559
8143
  async function handleRecordStop(params) {
8560
8144
  const sessionName = params.session || "default";
8145
+ const outputPath = params.output;
8561
8146
  const recorder = activeRecorders.get(sessionName);
8562
8147
  if (!recorder) {
8563
8148
  const existingData = SessionRecorder.readData(sessionName);
8564
8149
  if (existingData) {
8150
+ if (outputPath) {
8151
+ const { writeFileSync: writeFileSync7 } = await import("fs");
8152
+ writeFileSync7(outputPath, JSON.stringify(existingData, null, 2), "utf-8");
8153
+ }
8565
8154
  return {
8566
8155
  ok: true,
8567
8156
  message: "Recorder process already exited. Recording data found on disk.",
8157
+ output: outputPath,
8568
8158
  session: sessionName,
8569
8159
  actions: existingData.actions.length,
8570
8160
  network: existingData.network.length
@@ -8575,8 +8165,20 @@ function createRPCHandler() {
8575
8165
  try {
8576
8166
  const { data, summary } = await recorder.stop();
8577
8167
  activeRecorders.delete(sessionName);
8168
+ if (outputPath) {
8169
+ const { writeFileSync: writeFileSync7, mkdirSync: mkdirSync7 } = await import("fs");
8170
+ const { dirname: dirname2 } = await import("path");
8171
+ mkdirSync7(dirname2(outputPath), { recursive: true });
8172
+ if (outputPath.endsWith(".yaml") || outputPath.endsWith(".yml")) {
8173
+ const yaml2 = (await import("yaml")).default;
8174
+ writeFileSync7(outputPath, yaml2.stringify(data), "utf-8");
8175
+ } else {
8176
+ writeFileSync7(outputPath, JSON.stringify(data, null, 2), "utf-8");
8177
+ }
8178
+ }
8578
8179
  return {
8579
8180
  ok: true,
8181
+ output: outputPath,
8580
8182
  session: sessionName,
8581
8183
  actions: data.actions.length,
8582
8184
  network: data.network.length,
@@ -8665,8 +8267,13 @@ function createRPCHandler() {
8665
8267
  let rawContent;
8666
8268
  let parsed;
8667
8269
  try {
8668
- rawContent = readFileSync6(file, "utf8");
8669
- parsed = JSON.parse(rawContent);
8270
+ rawContent = readFileSync5(file, "utf8");
8271
+ try {
8272
+ parsed = JSON.parse(rawContent);
8273
+ } catch {
8274
+ const yaml2 = (await import("yaml")).default;
8275
+ parsed = yaml2.parse(rawContent);
8276
+ }
8670
8277
  } catch (e) {
8671
8278
  return { ok: false, success: false, duration: 0, eventsPlayed: 0, totalEvents: 0, errors: [{ eventIndex: -1, error: "Failed to read/parse file: " + String(e) }] };
8672
8279
  }
@@ -8709,8 +8316,8 @@ function createRPCHandler() {
8709
8316
  const result = await engine.play({
8710
8317
  slowMo,
8711
8318
  onCheckpoint: async (checkpoint) => {
8712
- return new Promise((resolve3) => {
8713
- replayResumeResolvers.set(sessionName, () => resolve3(true));
8319
+ return new Promise((resolve) => {
8320
+ replayResumeResolvers.set(sessionName, () => resolve(true));
8714
8321
  console.log(`[replay] Checkpoint reached: [${checkpoint.type}] ${checkpoint.hint}`);
8715
8322
  console.log('[replay] Send "replay:resume" RPC to continue.');
8716
8323
  });
@@ -9774,13 +9381,13 @@ var FileListHandler = class {
9774
9381
  async handle(ctx) {
9775
9382
  const msg = ctx.message;
9776
9383
  try {
9777
- const { readdirSync: readdirSync2, statSync } = await import("fs");
9778
- const { join: join10, resolve: resolve3 } = await import("path");
9779
- const targetPath = resolve3(msg.path);
9780
- const entries = readdirSync2(targetPath);
9384
+ const { readdirSync, statSync } = await import("fs");
9385
+ const { join: join9, resolve } = await import("path");
9386
+ const targetPath = resolve(msg.path);
9387
+ const entries = readdirSync(targetPath);
9781
9388
  const files = entries.map((name) => {
9782
9389
  try {
9783
- const stat = statSync(join10(targetPath, name));
9390
+ const stat = statSync(join9(targetPath, name));
9784
9391
  return { name, isDir: stat.isDirectory(), size: stat.size, modified: stat.mtime.toISOString() };
9785
9392
  } catch {
9786
9393
  return { name, isDir: false, size: 0, modified: "" };
@@ -9797,10 +9404,10 @@ var FileDownloadHandler = class {
9797
9404
  async handle(ctx) {
9798
9405
  const msg = ctx.message;
9799
9406
  try {
9800
- const { readFileSync: readFileSync7 } = await import("fs");
9801
- const { resolve: resolve3, basename } = await import("path");
9802
- const targetPath = resolve3(msg.path);
9803
- const data = readFileSync7(targetPath);
9407
+ const { readFileSync: readFileSync6 } = await import("fs");
9408
+ const { resolve, basename } = await import("path");
9409
+ const targetPath = resolve(msg.path);
9410
+ const data = readFileSync6(targetPath);
9804
9411
  const base64 = data.toString("base64");
9805
9412
  const ext = targetPath.split(".").pop()?.toLowerCase() || "";
9806
9413
  const mimeMap = {
@@ -10104,7 +9711,7 @@ var WSServer = class extends EventEmitter2 {
10104
9711
  this.isRunning = false;
10105
9712
  return;
10106
9713
  }
10107
- return new Promise((resolve3, reject) => {
9714
+ return new Promise((resolve, reject) => {
10108
9715
  this.wsServer.close((err) => {
10109
9716
  if (err) {
10110
9717
  reject(err);
@@ -10112,7 +9719,7 @@ var WSServer = class extends EventEmitter2 {
10112
9719
  this.wsServer = null;
10113
9720
  this.isRunning = false;
10114
9721
  this.emit("stopped");
10115
- resolve3();
9722
+ resolve();
10116
9723
  }
10117
9724
  });
10118
9725
  });
@@ -11441,8 +11048,8 @@ connectWS();
11441
11048
  }
11442
11049
 
11443
11050
  // src/daemon/daemon-main.ts
11444
- var CONFIG_DIR4 = join9(homedir9(), ".xbrowser");
11445
- var LOG_FILE = join9(CONFIG_DIR4, "daemon.log");
11051
+ var CONFIG_DIR4 = join8(homedir8(), ".xbrowser");
11052
+ var LOG_FILE = join8(CONFIG_DIR4, "daemon.log");
11446
11053
  function log(msg) {
11447
11054
  const ts = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").substring(0, 19);
11448
11055
  const line = `[DAEMON ${ts}] ${msg}
@@ -11474,7 +11081,7 @@ async function main() {
11474
11081
  if (err.code === "EADDRINUSE") {
11475
11082
  log(`Port ${daemonPort} already in use \u2014 another daemon instance likely won the startup race. Exiting gracefully.`);
11476
11083
  try {
11477
- unlinkSync(join9(CONFIG_DIR4, "daemon.json"));
11084
+ unlinkSync(join8(CONFIG_DIR4, "daemon.json"));
11478
11085
  } catch {
11479
11086
  }
11480
11087
  process.exit(0);
@@ -11508,8 +11115,8 @@ async function main() {
11508
11115
  rpcHandler.setPreviewWS(previewWS);
11509
11116
  previewWS.on("screencast-started", (sid) => log(`Preview screencast started: ${sid}`));
11510
11117
  previewWS.on("screencast-stopped", (sid) => log(`Preview screencast stopped: ${sid}`));
11511
- mkdirSync7(CONFIG_DIR4, { recursive: true });
11512
- writeFileSync8(join9(CONFIG_DIR4, "daemon.json"), JSON.stringify({
11118
+ mkdirSync6(CONFIG_DIR4, { recursive: true });
11119
+ writeFileSync6(join8(CONFIG_DIR4, "daemon.json"), JSON.stringify({
11513
11120
  port: daemonPort,
11514
11121
  pid: process.pid,
11515
11122
  startedAt: Date.now()