@xbrowser/cli 1.2.1 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1497,6 +1497,16 @@ declare class PluginInstaller {
1497
1497
  * @throws If the plugin already exists without `force`, or if installation fails.
1498
1498
  */
1499
1499
  install(source: string, options?: InstallOptions): Promise<InstalledPlugin>;
1500
+ /**
1501
+ * Fix missing `../shared/` dependencies after installation.
1502
+ *
1503
+ * Some marketplace/npm packages import from `../shared/` (e.g. ssr-detect.js,
1504
+ * ai-chat-base.ts) but the `shared/` directory is not included in the package.
1505
+ * This method scans the installed plugin's index.ts for such imports and
1506
+ * copies the missing files from the local repository's `.xcli/plugins/shared/`
1507
+ * directory (if available).
1508
+ */
1509
+ private fixSharedDeps;
1500
1510
  installFromMarketplace(slug: string, options?: InstallOptions): Promise<InstalledPlugin>;
1501
1511
  installWithMarketplaceFallback(source: string, options?: InstallOptions): Promise<InstalledPlugin>;
1502
1512
  /**
package/dist/index.js CHANGED
@@ -25,7 +25,7 @@ import {
25
25
  killAllDaemonProcesses,
26
26
  startDaemonProcess,
27
27
  stopDaemonProcess
28
- } from "./chunk-XYXCS7JW.js";
28
+ } from "./chunk-JPSFUFPG.js";
29
29
  import {
30
30
  CaptchaDetector,
31
31
  HumanInteractionManager,
@@ -989,10 +989,10 @@ var setCookieCommand = registerCommand({
989
989
  description: "Set a cookie",
990
990
  scope: "page",
991
991
  parameters: z8.object({
992
- name: z8.string(),
993
- value: z8.string(),
994
- domain: z8.string().optional(),
995
- path: z8.string().optional(),
992
+ name: z8.coerce.string(),
993
+ value: z8.coerce.string(),
994
+ domain: z8.coerce.string().optional(),
995
+ path: z8.coerce.string().optional(),
996
996
  expires: z8.number().optional(),
997
997
  httpOnly: z8.boolean().optional(),
998
998
  secure: z8.boolean().optional(),
@@ -6648,8 +6648,10 @@ var XBrowserPluginLoader = class {
6648
6648
  const instance = await this.loadPlugin(indexPath, entry.name);
6649
6649
  loaded.push(instance);
6650
6650
  } catch (err) {
6651
- if (process.env.XBROWSER_DEBUG) {
6652
- console.warn(`\u26A0\uFE0F Plugin "${entry.name}" load failed: ${err instanceof Error ? err.message : String(err)}`);
6651
+ const errMsg2 = err instanceof Error ? err.message : String(err);
6652
+ console.warn(`\u26A0\uFE0F Plugin "${entry.name}" load failed: ${errMsg2}`);
6653
+ if (errMsg2.includes("Cannot find module") && errMsg2.includes("shared/")) {
6654
+ console.warn(` \u{1F4A1} This plugin needs shared/ dependencies. Try: xbrowser plugin install shared`);
6653
6655
  }
6654
6656
  }
6655
6657
  }
@@ -7368,7 +7370,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
7368
7370
  params = result.data;
7369
7371
  }
7370
7372
  if (command.scope !== "cli" && !process.env.XBROWSER_DAEMON_WORKER) {
7371
- const { forwardExec } = await import("./daemon-client-ZHO6NG36.js");
7373
+ const { forwardExec } = await import("./daemon-client-XXKMJZZ7.js");
7372
7374
  const result = await forwardExec(commandName, params, sessionName, extraOpts?.cdpEndpoint);
7373
7375
  if (result) return result;
7374
7376
  }
@@ -8069,9 +8071,12 @@ import {
8069
8071
  existsSync as existsSync10,
8070
8072
  readdirSync as readdirSync2,
8071
8073
  mkdirSync as mkdirSync8,
8072
- rmSync as rmSync6
8074
+ rmSync as rmSync6,
8075
+ copyFileSync,
8076
+ cpSync as cpSync6,
8077
+ readFileSync as readFileSync8
8073
8078
  } from "fs";
8074
- import { resolve as resolve8, basename as basename2 } from "path";
8079
+ import { resolve as resolve8, basename as basename2, dirname as dirname3 } from "path";
8075
8080
  import { homedir as homedir7 } from "os";
8076
8081
 
8077
8082
  // src/plugin/install-sources/local.ts
@@ -8564,28 +8569,103 @@ var PluginInstaller = class {
8564
8569
  switch (type) {
8565
8570
  case "local":
8566
8571
  return await installFromLocal(source, name, targetDir).then((r) => {
8572
+ this.fixSharedDeps(targetDir);
8567
8573
  ensurePluginDependencies(this.pluginsDir);
8568
8574
  return r;
8569
8575
  });
8570
8576
  case "npm":
8571
8577
  return await installFromNpm(resolvedSource, name, targetDir).then((r) => {
8578
+ this.fixSharedDeps(targetDir);
8572
8579
  ensurePluginDependencies(this.pluginsDir);
8573
8580
  return r;
8574
8581
  });
8575
8582
  case "git":
8576
8583
  return await installFromGit(source, name, targetDir).then((r) => {
8584
+ this.fixSharedDeps(targetDir);
8577
8585
  ensurePluginDependencies(this.pluginsDir);
8578
8586
  return r;
8579
8587
  });
8580
8588
  case "url":
8581
8589
  return await installFromUrl(source, name, targetDir).then((r) => {
8590
+ this.fixSharedDeps(targetDir);
8582
8591
  ensurePluginDependencies(this.pluginsDir);
8583
8592
  return r;
8584
8593
  });
8585
8594
  }
8586
8595
  }
8596
+ /**
8597
+ * Fix missing `../shared/` dependencies after installation.
8598
+ *
8599
+ * Some marketplace/npm packages import from `../shared/` (e.g. ssr-detect.js,
8600
+ * ai-chat-base.ts) but the `shared/` directory is not included in the package.
8601
+ * This method scans the installed plugin's index.ts for such imports and
8602
+ * copies the missing files from the local repository's `.xcli/plugins/shared/`
8603
+ * directory (if available).
8604
+ */
8605
+ fixSharedDeps(pluginDir) {
8606
+ const indexPath = resolve8(pluginDir, "index.ts");
8607
+ if (!existsSync10(indexPath)) return;
8608
+ let content;
8609
+ try {
8610
+ content = readFileSync8(indexPath, "utf8");
8611
+ } catch {
8612
+ return;
8613
+ }
8614
+ const sharedImportRegex = /from\s+['"]\.\.\/shared\/([^'"]+)['"]/g;
8615
+ const missingFiles = [];
8616
+ let match;
8617
+ while ((match = sharedImportRegex.exec(content)) !== null) {
8618
+ missingFiles.push(match[1]);
8619
+ }
8620
+ if (missingFiles.length === 0) return;
8621
+ const sharedDir = resolve8(pluginDir, "..", "shared");
8622
+ const toCopy = [];
8623
+ for (const file of missingFiles) {
8624
+ const targetPath = resolve8(sharedDir, file);
8625
+ if (!existsSync10(targetPath)) {
8626
+ toCopy.push(file);
8627
+ }
8628
+ }
8629
+ if (toCopy.length === 0) return;
8630
+ const repoSharedDirs = [
8631
+ resolve8(process.cwd(), ".xcli/plugins/shared"),
8632
+ resolve8(homedir7(), ".xbrowser/plugins/shared")
8633
+ ];
8634
+ let sourceSharedDir = null;
8635
+ for (const dir of repoSharedDirs) {
8636
+ if (existsSync10(dir)) {
8637
+ sourceSharedDir = dir;
8638
+ break;
8639
+ }
8640
+ }
8641
+ if (!sourceSharedDir) {
8642
+ console.warn(`\u26A0\uFE0F Plugin "${basename2(pluginDir)}" imports shared files but they are missing: ${toCopy.join(", ")}`);
8643
+ console.warn(` To fix: install the "shared" plugin or copy .xcli/plugins/shared/ to ~/.xbrowser/plugins/shared/`);
8644
+ return;
8645
+ }
8646
+ mkdirSync8(sharedDir, { recursive: true });
8647
+ for (const file of toCopy) {
8648
+ const src = resolve8(sourceSharedDir, file);
8649
+ const dst = resolve8(sharedDir, file);
8650
+ if (existsSync10(src)) {
8651
+ try {
8652
+ cpSync6(dirname3(src), dirname3(dst), { recursive: true });
8653
+ console.log(`\u2705 Copied shared/${file} for plugin "${basename2(pluginDir)}"`);
8654
+ } catch {
8655
+ try {
8656
+ copyFileSync(src, dst);
8657
+ console.log(`\u2705 Copied shared/${file} for plugin "${basename2(pluginDir)}"`);
8658
+ } catch {
8659
+ console.warn(`\u26A0\uFE0F Could not copy shared/${file}`);
8660
+ }
8661
+ }
8662
+ }
8663
+ }
8664
+ }
8587
8665
  async installFromMarketplace(slug, options) {
8588
8666
  const result = await installFromMarketplace(this.pluginsDir, slug, options);
8667
+ const targetDir = resolve8(this.pluginsDir, result.name);
8668
+ this.fixSharedDeps(targetDir);
8589
8669
  ensurePluginDependencies(this.pluginsDir);
8590
8670
  return result;
8591
8671
  }
@@ -10612,6 +10692,18 @@ async function handlePlugin(args, options, mode) {
10612
10692
  await (await getPluginLoader()).reloadPlugin(result.name);
10613
10693
  } catch {
10614
10694
  }
10695
+ try {
10696
+ const { daemonPing } = await import("./daemon-client-XXKMJZZ7.js");
10697
+ if (await daemonPing()) {
10698
+ await fetch("http://localhost:9224/rpc", {
10699
+ method: "POST",
10700
+ headers: { "Content-Type": "application/json" },
10701
+ body: JSON.stringify({ method: "plugins:reload", params: {} }),
10702
+ signal: AbortSignal.timeout(5e3)
10703
+ });
10704
+ }
10705
+ } catch {
10706
+ }
10615
10707
  outputResult(
10616
10708
  { ok: true, name: result.name, source: result.source, path: result.path },
10617
10709
  mode
@@ -10767,7 +10859,8 @@ async function handleRecord(args, options, mode) {
10767
10859
  }
10768
10860
  case "stop": {
10769
10861
  const sessionName = options.session || "default";
10770
- const result = await forwardRecordStop(sessionName);
10862
+ const output = options.output || options.o;
10863
+ const result = await forwardRecordStop(sessionName, output);
10771
10864
  if (!result.ok) {
10772
10865
  outputError(String(result.error || "Failed to stop recording"));
10773
10866
  return;
@@ -10776,6 +10869,7 @@ async function handleRecord(args, options, mode) {
10776
10869
  ok: true,
10777
10870
  message: "Recording stopped.",
10778
10871
  sessionName,
10872
+ output: result.output || (output || SessionRecorder.getRecordingsDir(sessionName) + "/recording.json"),
10779
10873
  actions: result.actions,
10780
10874
  network: result.network,
10781
10875
  durationMs: result.durationMs,
@@ -11639,7 +11733,7 @@ async function handleNetCommand(args, options, mode, sessionName) {
11639
11733
 
11640
11734
  // src/cli/test-routes.ts
11641
11735
  import { execSync as execSync3 } from "child_process";
11642
- import { readFileSync as readFileSync8 } from "fs";
11736
+ import { readFileSync as readFileSync9 } from "fs";
11643
11737
  import { resolve as resolve9 } from "path";
11644
11738
  function findPluginPath(plugin) {
11645
11739
  const candidates = [
@@ -11648,7 +11742,7 @@ function findPluginPath(plugin) {
11648
11742
  ];
11649
11743
  for (const p of candidates) {
11650
11744
  try {
11651
- readFileSync8(p, "utf-8");
11745
+ readFileSync9(p, "utf-8");
11652
11746
  return p;
11653
11747
  } catch {
11654
11748
  }
@@ -11659,7 +11753,7 @@ function extractSchema(plugin, command) {
11659
11753
  const pluginPath = findPluginPath(plugin);
11660
11754
  let src;
11661
11755
  try {
11662
- src = readFileSync8(pluginPath, "utf-8");
11756
+ src = readFileSync9(pluginPath, "utf-8");
11663
11757
  } catch {
11664
11758
  return null;
11665
11759
  }
@@ -12889,7 +12983,7 @@ Run "xbrowser ${command} ${subCommand} --help" to see available parameters.`
12889
12983
  }
12890
12984
  const needsBrowser = cmdEntry.scope === "page" || cmdEntry.scope === "browser";
12891
12985
  if (needsBrowser && !process.env.XBROWSER_DAEMON_WORKER) {
12892
- const { forwardExec } = await import("./daemon-client-ZHO6NG36.js");
12986
+ const { forwardExec } = await import("./daemon-client-XXKMJZZ7.js");
12893
12987
  const userTimeout = typeof params.timeout === "number" && params.timeout > 0 ? params.timeout * 1e3 + 3e4 : void 0;
12894
12988
  const result = await forwardExec(`${command}.${subCommand}`, params, sessionName, cdpEndpoint, userTimeout);
12895
12989
  const resultData = result && typeof result === "object" && "data" in result ? result.data : void 0;
@@ -14437,10 +14531,10 @@ var FileDownloadHandler = class {
14437
14531
  async handle(ctx) {
14438
14532
  const msg = ctx.message;
14439
14533
  try {
14440
- const { readFileSync: readFileSync10 } = await import("fs");
14534
+ const { readFileSync: readFileSync11 } = await import("fs");
14441
14535
  const { resolve: resolve10, basename: basename3 } = await import("path");
14442
14536
  const targetPath = resolve10(msg.path);
14443
- const data = readFileSync10(targetPath);
14537
+ const data = readFileSync11(targetPath);
14444
14538
  const base64 = data.toString("base64");
14445
14539
  const ext = targetPath.split(".").pop()?.toLowerCase() || "";
14446
14540
  const mimeMap = {
@@ -0,0 +1,9 @@
1
+ import {
2
+ getPluginLoader,
3
+ resetPluginLoader
4
+ } from "./chunk-2QQDTXDL.js";
5
+ import "./chunk-KFQGP6VL.js";
6
+ export {
7
+ getPluginLoader,
8
+ resetPluginLoader
9
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xbrowser/cli",
3
- "version": "1.2.1",
3
+ "version": "1.3.0",
4
4
  "description": "Browser automation CLI for web scraping, headless browsing, SEO analysis, and AI agent workflows. A command-line alternative to Playwright, Puppeteer, and Selenium.",
5
5
  "type": "module",
6
6
  "bin": {