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