@xbrowser/cli 0.15.0 → 0.16.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.js CHANGED
@@ -107,7 +107,8 @@ import {
107
107
  isCommandResult,
108
108
  configureArchiveStore,
109
109
  appendCommandToArchive,
110
- checkGuard
110
+ checkGuard,
111
+ PluginStorage
111
112
  } from "@dyyz1993/xcli-core";
112
113
 
113
114
  // src/utils/positional-params.ts
@@ -5962,7 +5963,7 @@ import { join as join2 } from "path";
5962
5963
  import { execSync as execSync6 } from "child_process";
5963
5964
  var SHARED_PLUGIN_DEPENDENCIES = {
5964
5965
  "zod": "^3.24.0",
5965
- "@dyyz1993/xcli-core": "^0.9.2"
5966
+ "@dyyz1993/xcli-core": "^0.12.1"
5966
5967
  };
5967
5968
  function ensurePluginDependencies(pluginsDir) {
5968
5969
  const zodPath = join2(pluginsDir, "node_modules", "zod");
@@ -6546,45 +6547,13 @@ async function loadHooks() {
6546
6547
  // src/executor.ts
6547
6548
  import { homedir as homedir3 } from "os";
6548
6549
  import { join as join3 } from "path";
6549
- import { existsSync as existsSync6, readFileSync as readFileSync9, writeFileSync as writeFileSync4, mkdirSync as mkdirSync3 } from "fs";
6550
6550
  var NAVIGATION_COMMANDS = /* @__PURE__ */ new Set(["goto", "back", "forward", "refresh"]);
6551
6551
  var snapshotHintShown = /* @__PURE__ */ new WeakSet();
6552
6552
  var STORAGE_DIR = join3(homedir3(), ".xbrowser", "storage");
6553
6553
  var storageCache = /* @__PURE__ */ new Map();
6554
6554
  function getPluginStorage(pluginName) {
6555
6555
  if (!storageCache.has(pluginName)) {
6556
- const filePath = join3(STORAGE_DIR, `${pluginName}.json`);
6557
- let data = {};
6558
- const load3 = () => {
6559
- if (existsSync6(filePath)) {
6560
- try {
6561
- data = JSON.parse(readFileSync9(filePath, "utf-8"));
6562
- } catch {
6563
- data = {};
6564
- }
6565
- }
6566
- };
6567
- const save = () => {
6568
- mkdirSync3(STORAGE_DIR, { recursive: true });
6569
- writeFileSync4(filePath, JSON.stringify(data, null, 2), "utf-8");
6570
- };
6571
- load3();
6572
- storageCache.set(pluginName, {
6573
- get: async (key) => data[key] ?? null,
6574
- set: async (key, value) => {
6575
- data[key] = value;
6576
- save();
6577
- },
6578
- delete: async (key) => {
6579
- delete data[key];
6580
- save();
6581
- },
6582
- clear: async () => {
6583
- data = {};
6584
- save();
6585
- },
6586
- keys: async () => Object.keys(data)
6587
- });
6556
+ storageCache.set(pluginName, new PluginStorage(pluginName, STORAGE_DIR));
6588
6557
  }
6589
6558
  return storageCache.get(pluginName);
6590
6559
  }
@@ -6684,6 +6653,10 @@ async function executeCommand(commandName, params, sessionName = "default", extr
6684
6653
  session = await createSession(sessionName, params.url, {
6685
6654
  cdpEndpoint: extraOpts?.cdpEndpoint
6686
6655
  });
6656
+ } else if (command.scope === "browser") {
6657
+ session = await createSession(sessionName, void 0, {
6658
+ cdpEndpoint: extraOpts?.cdpEndpoint
6659
+ });
6687
6660
  } else if (command.scope !== "project") {
6688
6661
  return errorResult(
6689
6662
  `Session '${sessionName}' not found. Run "xbrowser session open <url>" first.`
@@ -7333,26 +7306,26 @@ var configBuiltin = {
7333
7306
 
7334
7307
  // src/plugin/installer.ts
7335
7308
  import {
7336
- existsSync as existsSync13,
7309
+ existsSync as existsSync12,
7337
7310
  readdirSync as readdirSync3,
7338
- mkdirSync as mkdirSync8,
7311
+ mkdirSync as mkdirSync7,
7339
7312
  rmSync as rmSync7
7340
7313
  } from "fs";
7341
7314
  import { resolve as resolve15, basename as basename2 } from "path";
7342
7315
  import { homedir as homedir4 } from "os";
7343
7316
 
7344
7317
  // src/plugin/install-sources/local.ts
7345
- import { existsSync as existsSync8, cpSync as cpSync2, rmSync as rmSync2 } from "fs";
7318
+ import { existsSync as existsSync7, cpSync as cpSync2, rmSync as rmSync2 } from "fs";
7346
7319
  import { resolve as resolve10 } from "path";
7347
7320
 
7348
7321
  // src/plugin/install-utils.ts
7349
7322
  import {
7350
- existsSync as existsSync7,
7323
+ existsSync as existsSync6,
7351
7324
  readdirSync as readdirSync2,
7352
7325
  cpSync,
7353
7326
  rmSync,
7354
- mkdirSync as mkdirSync4,
7355
- readFileSync as readFileSync10,
7327
+ mkdirSync as mkdirSync3,
7328
+ readFileSync as readFileSync9,
7356
7329
  createWriteStream
7357
7330
  } from "fs";
7358
7331
  import { resolve as resolve9 } from "path";
@@ -7423,7 +7396,7 @@ async function downloadToFile(url, destPath) {
7423
7396
  await pipeline(nodeStream, createWriteStream(destPath));
7424
7397
  }
7425
7398
  function extractTarGz(tarballPath, targetDir) {
7426
- mkdirSync4(targetDir, { recursive: true });
7399
+ mkdirSync3(targetDir, { recursive: true });
7427
7400
  execSync7(`tar -xzf "${tarballPath}" -C "${targetDir}"`, { stdio: "pipe" });
7428
7401
  }
7429
7402
  function flattenPackageRoot(targetDir) {
@@ -7444,18 +7417,18 @@ function flattenPackageRoot(targetDir) {
7444
7417
  async function verifyPlugin(dir) {
7445
7418
  const warnings = [];
7446
7419
  const indexPath = resolve9(dir, "index.ts");
7447
- if (!existsSync7(indexPath)) {
7420
+ if (!existsSync6(indexPath)) {
7448
7421
  const indexJs = resolve9(dir, "index.js");
7449
- if (!existsSync7(indexJs)) {
7422
+ if (!existsSync6(indexJs)) {
7450
7423
  return { valid: false, error: "No index.ts or index.js entry point found", warnings };
7451
7424
  }
7452
7425
  }
7453
7426
  const pkgPath = resolve9(dir, "package.json");
7454
- if (!existsSync7(pkgPath)) {
7427
+ if (!existsSync6(pkgPath)) {
7455
7428
  warnings.push("No package.json found");
7456
7429
  } else {
7457
7430
  try {
7458
- const pkg2 = JSON.parse(readFileSync10(pkgPath, "utf-8"));
7431
+ const pkg2 = JSON.parse(readFileSync9(pkgPath, "utf-8"));
7459
7432
  if (!pkg2.xbrowser) {
7460
7433
  warnings.push("No xbrowser metadata in package.json");
7461
7434
  }
@@ -7475,7 +7448,7 @@ function safeCleanup(dir) {
7475
7448
  // src/plugin/install-sources/local.ts
7476
7449
  async function installFromLocal(source, name, targetDir) {
7477
7450
  const srcPath = resolve10(source);
7478
- if (!existsSync8(srcPath)) {
7451
+ if (!existsSync7(srcPath)) {
7479
7452
  throw new Error(`Local path does not exist: ${srcPath}`);
7480
7453
  }
7481
7454
  const tmpTarget = `${targetDir}-tmp-${Date.now()}`;
@@ -7488,7 +7461,7 @@ async function installFromLocal(source, name, targetDir) {
7488
7461
  safeCleanup(tmpTarget);
7489
7462
  throw new Error(`Invalid plugin: ${verify.error}`);
7490
7463
  }
7491
- if (existsSync8(targetDir)) {
7464
+ if (existsSync7(targetDir)) {
7492
7465
  rmSync2(targetDir, { recursive: true, force: true });
7493
7466
  }
7494
7467
  cpSync2(tmpTarget, targetDir, { recursive: true, force: true });
@@ -7508,7 +7481,7 @@ async function installFromLocal(source, name, targetDir) {
7508
7481
  }
7509
7482
 
7510
7483
  // src/plugin/install-sources/npm.ts
7511
- import { existsSync as existsSync9, mkdirSync as mkdirSync5, readFileSync as readFileSync11, writeFileSync as writeFileSync5, rmSync as rmSync3, cpSync as cpSync3 } from "fs";
7484
+ import { existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync10, writeFileSync as writeFileSync4, rmSync as rmSync3, cpSync as cpSync3 } from "fs";
7512
7485
  import { resolve as resolve11, join as join4 } from "path";
7513
7486
  import { tmpdir } from "os";
7514
7487
  async function installFromNpm(packageName, name, targetDir) {
@@ -7529,7 +7502,7 @@ async function installFromNpm(packageName, name, targetDir) {
7529
7502
  }
7530
7503
  const tarballUrl = versionMeta.dist.tarball;
7531
7504
  const tmpDir = join4(tmpdir(), `xbrowser-npm-${Date.now()}`);
7532
- mkdirSync5(tmpDir, { recursive: true });
7505
+ mkdirSync4(tmpDir, { recursive: true });
7533
7506
  let warnings = [];
7534
7507
  try {
7535
7508
  const tarballPath = join4(tmpDir, `${name}.tgz`);
@@ -7542,16 +7515,16 @@ async function installFromNpm(packageName, name, targetDir) {
7542
7515
  if (!verify.valid) {
7543
7516
  throw new Error(`Invalid npm plugin: ${verify.error}`);
7544
7517
  }
7545
- if (existsSync9(targetDir)) {
7518
+ if (existsSync8(targetDir)) {
7546
7519
  rmSync3(targetDir, { recursive: true, force: true });
7547
7520
  }
7548
7521
  cpSync3(extractDir, targetDir, { recursive: true, force: true });
7549
7522
  const pkgPath = resolve11(targetDir, "package.json");
7550
- if (existsSync9(pkgPath)) {
7551
- const pkg2 = JSON.parse(readFileSync11(pkgPath, "utf-8"));
7523
+ if (existsSync8(pkgPath)) {
7524
+ const pkg2 = JSON.parse(readFileSync10(pkgPath, "utf-8"));
7552
7525
  if (!pkg2._npmSource) {
7553
7526
  pkg2._npmSource = { name: packageName, version: latestVersion };
7554
- writeFileSync5(pkgPath, JSON.stringify(pkg2, null, 2));
7527
+ writeFileSync4(pkgPath, JSON.stringify(pkg2, null, 2));
7555
7528
  }
7556
7529
  }
7557
7530
  } finally {
@@ -7568,7 +7541,7 @@ async function installFromNpm(packageName, name, targetDir) {
7568
7541
  }
7569
7542
 
7570
7543
  // src/plugin/install-sources/git.ts
7571
- import { existsSync as existsSync10, readFileSync as readFileSync12, writeFileSync as writeFileSync6, rmSync as rmSync4, cpSync as cpSync4 } from "fs";
7544
+ import { existsSync as existsSync9, readFileSync as readFileSync11, writeFileSync as writeFileSync5, rmSync as rmSync4, cpSync as cpSync4 } from "fs";
7572
7545
  import { resolve as resolve12, join as join5 } from "path";
7573
7546
  import { tmpdir as tmpdir2 } from "os";
7574
7547
  import { execSync as execSync8 } from "child_process";
@@ -7582,17 +7555,17 @@ async function installFromGit(gitUrl, name, targetDir) {
7582
7555
  if (!verify.valid) {
7583
7556
  throw new Error(`Invalid git plugin: ${verify.error}`);
7584
7557
  }
7585
- if (existsSync10(targetDir)) {
7558
+ if (existsSync9(targetDir)) {
7586
7559
  rmSync4(targetDir, { recursive: true, force: true });
7587
7560
  }
7588
7561
  cpSync4(tmpDir, targetDir, { recursive: true, force: true });
7589
7562
  rmSync4(resolve12(targetDir, ".git"), { recursive: true, force: true });
7590
7563
  const pkgPath = resolve12(targetDir, "package.json");
7591
- if (existsSync10(pkgPath)) {
7592
- const pkg2 = JSON.parse(readFileSync12(pkgPath, "utf-8"));
7564
+ if (existsSync9(pkgPath)) {
7565
+ const pkg2 = JSON.parse(readFileSync11(pkgPath, "utf-8"));
7593
7566
  if (!pkg2._gitSource) {
7594
7567
  pkg2._gitSource = { url: gitUrl };
7595
- writeFileSync6(pkgPath, JSON.stringify(pkg2, null, 2));
7568
+ writeFileSync5(pkgPath, JSON.stringify(pkg2, null, 2));
7596
7569
  }
7597
7570
  }
7598
7571
  } finally {
@@ -7609,12 +7582,12 @@ async function installFromGit(gitUrl, name, targetDir) {
7609
7582
  }
7610
7583
 
7611
7584
  // src/plugin/install-sources/url.ts
7612
- import { existsSync as existsSync11, readFileSync as readFileSync13, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, rmSync as rmSync5, cpSync as cpSync5 } from "fs";
7585
+ import { existsSync as existsSync10, readFileSync as readFileSync12, writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, rmSync as rmSync5, cpSync as cpSync5 } from "fs";
7613
7586
  import { resolve as resolve13, join as join6, basename } from "path";
7614
7587
  import { tmpdir as tmpdir3 } from "os";
7615
7588
  async function installFromUrl(url, name, targetDir) {
7616
7589
  const tmpDir = join6(tmpdir3(), `xbrowser-url-${Date.now()}`);
7617
- mkdirSync6(tmpDir, { recursive: true });
7590
+ mkdirSync5(tmpDir, { recursive: true });
7618
7591
  let warnings = [];
7619
7592
  try {
7620
7593
  const fileName = basename(new URL(url).pathname) || "plugin.tar.gz";
@@ -7628,16 +7601,16 @@ async function installFromUrl(url, name, targetDir) {
7628
7601
  if (!verify.valid) {
7629
7602
  throw new Error(`Invalid plugin from URL: ${verify.error}`);
7630
7603
  }
7631
- if (existsSync11(targetDir)) {
7604
+ if (existsSync10(targetDir)) {
7632
7605
  rmSync5(targetDir, { recursive: true, force: true });
7633
7606
  }
7634
7607
  cpSync5(extractDir, targetDir, { recursive: true, force: true });
7635
7608
  const pkgPath = resolve13(targetDir, "package.json");
7636
- if (existsSync11(pkgPath)) {
7637
- const pkg2 = JSON.parse(readFileSync13(pkgPath, "utf-8"));
7609
+ if (existsSync10(pkgPath)) {
7610
+ const pkg2 = JSON.parse(readFileSync12(pkgPath, "utf-8"));
7638
7611
  if (!pkg2._urlSource) {
7639
7612
  pkg2._urlSource = { url };
7640
- writeFileSync7(pkgPath, JSON.stringify(pkg2, null, 2));
7613
+ writeFileSync6(pkgPath, JSON.stringify(pkg2, null, 2));
7641
7614
  }
7642
7615
  }
7643
7616
  } finally {
@@ -7655,10 +7628,10 @@ async function installFromUrl(url, name, targetDir) {
7655
7628
 
7656
7629
  // src/plugin/install-sources/marketplace.ts
7657
7630
  import {
7658
- existsSync as existsSync12,
7659
- mkdirSync as mkdirSync7,
7660
- writeFileSync as writeFileSync8,
7661
- readFileSync as readFileSync14,
7631
+ existsSync as existsSync11,
7632
+ mkdirSync as mkdirSync6,
7633
+ writeFileSync as writeFileSync7,
7634
+ readFileSync as readFileSync13,
7662
7635
  rmSync as rmSync6,
7663
7636
  cpSync as cpSync6
7664
7637
  } from "fs";
@@ -7680,12 +7653,12 @@ async function installFromMarketplace(pluginsDir, slug, options) {
7680
7653
  const plugin = detailData.data;
7681
7654
  const name = options?.name || String(plugin.slug || slug);
7682
7655
  const targetDir = resolve14(pluginsDir, name);
7683
- if (existsSync12(targetDir) && !options?.force) {
7656
+ if (existsSync11(targetDir) && !options?.force) {
7684
7657
  throw new Error(`Plugin "${name}" already exists. Use --force to overwrite.`);
7685
7658
  }
7686
- mkdirSync7(targetDir, { recursive: true });
7659
+ mkdirSync6(targetDir, { recursive: true });
7687
7660
  const tmpDir = join7(tmpdir4(), `xbrowser-marketplace-${Date.now()}`);
7688
- mkdirSync7(tmpDir, { recursive: true });
7661
+ mkdirSync6(tmpDir, { recursive: true });
7689
7662
  const realSlug = String(plugin.slug || slug);
7690
7663
  try {
7691
7664
  await downloadAndExtractMarketplaceTarball(baseUrl, realSlug, tmpDir, targetDir);
@@ -7715,14 +7688,14 @@ function isManifestArray(data) {
7715
7688
  return Array.isArray(data) && data.length > 0 && typeof data[0].path === "string" && typeof data[0].content === "string";
7716
7689
  }
7717
7690
  function extractManifestToDir(manifest, targetDir) {
7718
- if (existsSync12(targetDir)) {
7691
+ if (existsSync11(targetDir)) {
7719
7692
  rmSync6(targetDir, { recursive: true, force: true });
7720
7693
  }
7721
- mkdirSync7(targetDir, { recursive: true });
7694
+ mkdirSync6(targetDir, { recursive: true });
7722
7695
  for (const file of manifest) {
7723
7696
  const filePath = resolve14(targetDir, file.path);
7724
- mkdirSync7(dirname2(filePath), { recursive: true });
7725
- writeFileSync8(filePath, Buffer.from(file.content, "base64"));
7697
+ mkdirSync6(dirname2(filePath), { recursive: true });
7698
+ writeFileSync7(filePath, Buffer.from(file.content, "base64"));
7726
7699
  }
7727
7700
  }
7728
7701
  function tryParseAsGzippedManifest(buffer) {
@@ -7749,7 +7722,7 @@ async function downloadAndExtractMarketplaceTarball(baseUrl, slug, tmpDir, targe
7749
7722
  const redirectUrl = tarballRes.headers.get("location");
7750
7723
  const tarballPath = join7(tmpDir, `${slug}.tar.gz`);
7751
7724
  await downloadToFile(redirectUrl, tarballPath);
7752
- const buffer = readFileSync14(tarballPath);
7725
+ const buffer = readFileSync13(tarballPath);
7753
7726
  const manifest = tryParseAsGzippedManifest(buffer);
7754
7727
  if (manifest) {
7755
7728
  extractManifestToDir(manifest, targetDir);
@@ -7758,7 +7731,7 @@ async function downloadAndExtractMarketplaceTarball(baseUrl, slug, tmpDir, targe
7758
7731
  const extractDir = join7(tmpDir, "extracted");
7759
7732
  extractTarGz(tarballPath, extractDir);
7760
7733
  flattenPackageRoot(extractDir);
7761
- if (existsSync12(targetDir)) {
7734
+ if (existsSync11(targetDir)) {
7762
7735
  rmSync6(targetDir, { recursive: true, force: true });
7763
7736
  }
7764
7737
  cpSync6(extractDir, targetDir, { recursive: true, force: true });
@@ -7770,12 +7743,12 @@ async function downloadAndExtractMarketplaceTarball(baseUrl, slug, tmpDir, targe
7770
7743
  return;
7771
7744
  }
7772
7745
  const tarballPath = join7(tmpDir, `${slug}.tar.gz`);
7773
- writeFileSync8(tarballPath, buffer);
7746
+ writeFileSync7(tarballPath, buffer);
7774
7747
  try {
7775
7748
  const extractDir = join7(tmpDir, "extracted");
7776
7749
  extractTarGz(tarballPath, extractDir);
7777
7750
  flattenPackageRoot(extractDir);
7778
- if (existsSync12(targetDir)) {
7751
+ if (existsSync11(targetDir)) {
7779
7752
  rmSync6(targetDir, { recursive: true, force: true });
7780
7753
  }
7781
7754
  cpSync6(extractDir, targetDir, { recursive: true, force: true });
@@ -7811,24 +7784,24 @@ function writeMarketplacePackageJson(plugin, slug, name, baseUrl, targetDir) {
7811
7784
  }
7812
7785
  };
7813
7786
  const pkgPath = resolve14(targetDir, "package.json");
7814
- if (!existsSync12(pkgPath)) {
7815
- writeFileSync8(pkgPath, JSON.stringify(packageJson, null, 2));
7787
+ if (!existsSync11(pkgPath)) {
7788
+ writeFileSync7(pkgPath, JSON.stringify(packageJson, null, 2));
7816
7789
  } else {
7817
7790
  try {
7818
- const existing = JSON.parse(readFileSync14(pkgPath, "utf-8"));
7791
+ const existing = JSON.parse(readFileSync13(pkgPath, "utf-8"));
7819
7792
  const merged = {
7820
7793
  ...existing,
7821
7794
  xbrowser: { ...existing.xbrowser, ...packageJson.xbrowser },
7822
7795
  _marketplace: packageJson._marketplace
7823
7796
  };
7824
- writeFileSync8(pkgPath, JSON.stringify(merged, null, 2));
7797
+ writeFileSync7(pkgPath, JSON.stringify(merged, null, 2));
7825
7798
  } catch {
7826
- writeFileSync8(pkgPath, JSON.stringify(packageJson, null, 2));
7799
+ writeFileSync7(pkgPath, JSON.stringify(packageJson, null, 2));
7827
7800
  }
7828
7801
  }
7829
7802
  }
7830
7803
  function ensureIndexFile(plugin, name, targetDir) {
7831
- if (!existsSync12(resolve14(targetDir, "index.ts")) && !existsSync12(resolve14(targetDir, "index.js"))) {
7804
+ if (!existsSync11(resolve14(targetDir, "index.ts")) && !existsSync11(resolve14(targetDir, "index.js"))) {
7832
7805
  const commands = plugin.commands || [];
7833
7806
  const commandHandlers = commands.length > 0 ? commands.map((cmd) => {
7834
7807
  return [
@@ -7843,7 +7816,7 @@ function ensureIndexFile(plugin, name, targetDir) {
7843
7816
  ` handler: async () => ({ data: { message: 'Hello from ${name}!' }, tips: [] }),`,
7844
7817
  ` });`
7845
7818
  ].join("\n");
7846
- writeFileSync8(
7819
+ writeFileSync7(
7847
7820
  resolve14(targetDir, "index.ts"),
7848
7821
  [
7849
7822
  `import type { XCLIAPI } from '@dyyz1993/xcli-core';`,
@@ -7882,10 +7855,10 @@ var PluginInstaller = class {
7882
7855
  const type = this.detectSourceType(source);
7883
7856
  const name = options?.name || this.deriveName(source, type);
7884
7857
  const targetDir = resolve15(this.pluginsDir, name);
7885
- if (existsSync13(targetDir) && !options?.force) {
7858
+ if (existsSync12(targetDir) && !options?.force) {
7886
7859
  throw new Error(`Plugin "${name}" already exists. Use --force to overwrite.`);
7887
7860
  }
7888
- mkdirSync8(targetDir, { recursive: true });
7861
+ mkdirSync7(targetDir, { recursive: true });
7889
7862
  const resolvedSource = type === "npm" ? await resolveNpmPackageWithFallback(source) : source;
7890
7863
  switch (type) {
7891
7864
  case "local":
@@ -7943,7 +7916,7 @@ var PluginInstaller = class {
7943
7916
  */
7944
7917
  async uninstall(name) {
7945
7918
  const targetDir = resolve15(this.pluginsDir, name);
7946
- if (!existsSync13(targetDir)) {
7919
+ if (!existsSync12(targetDir)) {
7947
7920
  throw new Error(`Plugin "${name}" not found`);
7948
7921
  }
7949
7922
  rmSync7(targetDir, { recursive: true, force: true });
@@ -7954,7 +7927,7 @@ var PluginInstaller = class {
7954
7927
  * @returns Array of installed plugin information.
7955
7928
  */
7956
7929
  async list(_options) {
7957
- if (!existsSync13(this.pluginsDir)) return [];
7930
+ if (!existsSync12(this.pluginsDir)) return [];
7958
7931
  const entries = readdirSync3(this.pluginsDir, { withFileTypes: true });
7959
7932
  const plugins = [];
7960
7933
  for (const entry of entries) {
@@ -7962,7 +7935,7 @@ var PluginInstaller = class {
7962
7935
  const pluginPath = resolve15(this.pluginsDir, entry.name);
7963
7936
  const indexPath = resolve15(pluginPath, "index.ts");
7964
7937
  const indexJsPath = resolve15(pluginPath, "index.js");
7965
- if (!existsSync13(indexPath) && !existsSync13(indexJsPath)) continue;
7938
+ if (!existsSync12(indexPath) && !existsSync12(indexJsPath)) continue;
7966
7939
  const metadata = PluginMetadataParser.parseFromPackageJson(pluginPath);
7967
7940
  let source = "local";
7968
7941
  const pkg2 = readJsonFile(resolve15(pluginPath, "package.json"), {});
@@ -7989,10 +7962,10 @@ var PluginInstaller = class {
7989
7962
  }
7990
7963
  if (source.startsWith("file://")) {
7991
7964
  const filePath = decodeURIComponent(new URL(source).pathname);
7992
- if (existsSync13(filePath)) return "url";
7965
+ if (existsSync12(filePath)) return "url";
7993
7966
  }
7994
7967
  if (source.endsWith(".git") || source.includes("github.com/")) return "git";
7995
- if (existsSync13(resolve15(source))) return "local";
7968
+ if (existsSync12(resolve15(source))) return "local";
7996
7969
  return "npm";
7997
7970
  }
7998
7971
  deriveName(source, type) {
@@ -10021,7 +9994,7 @@ async function handleFilter(args, _mode) {
10021
9994
 
10022
9995
  // src/stdin.ts
10023
9996
  import { createInterface } from "readline";
10024
- import { readFileSync as readFileSync15 } from "fs";
9997
+ import { readFileSync as readFileSync14 } from "fs";
10025
9998
  async function readStdin2() {
10026
9999
  if (process.stdin.isTTY) return [];
10027
10000
  const lines = [];
@@ -10035,7 +10008,7 @@ async function readStdin2() {
10035
10008
  return lines;
10036
10009
  }
10037
10010
  function readCommandFile(filePath) {
10038
- const content = readFileSync15(filePath, "utf-8");
10011
+ const content = readFileSync14(filePath, "utf-8");
10039
10012
  return content.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#"));
10040
10013
  }
10041
10014
 
@@ -10932,6 +10905,22 @@ var HTTPServer = class {
10932
10905
  };
10933
10906
 
10934
10907
  // src/router.ts
10908
+ var KNOWN_GLOBAL_OPTIONS = /* @__PURE__ */ new Set([
10909
+ "json",
10910
+ "yaml",
10911
+ "session",
10912
+ "cdp",
10913
+ "cdp-endpoint",
10914
+ "version",
10915
+ "v",
10916
+ "help",
10917
+ "h",
10918
+ "target",
10919
+ "port",
10920
+ "token",
10921
+ "timeout",
10922
+ "headless"
10923
+ ]);
10935
10924
  function showCommandHelp(siteName, cmd, siteConfig, mode) {
10936
10925
  const c = cmd;
10937
10926
  if (mode === "json") {
@@ -11056,8 +11045,23 @@ async function handleEvalMode(argv) {
11056
11045
  }
11057
11046
  async function handleChainInput(input, argv) {
11058
11047
  const cdpEndpoint = argv ? extractCdpFromArgv(argv) : void 0;
11048
+ const jsonMode = argv ? argv.includes("--json") || argv.includes("-j") : false;
11059
11049
  const chainResult = await executeChain(input, { cdpEndpoint });
11060
- printChainResult(chainResult);
11050
+ if (jsonMode) {
11051
+ const output = {
11052
+ success: chainResult.success,
11053
+ steps: chainResult.steps.map((s) => ({
11054
+ command: s.raw,
11055
+ success: s.success,
11056
+ data: s.data,
11057
+ duration: s.duration,
11058
+ ...s.hookOutputs?.length ? { hooks: s.hookOutputs } : {}
11059
+ }))
11060
+ };
11061
+ console.log(JSON.stringify(output, null, 2));
11062
+ } else {
11063
+ printChainResult(chainResult);
11064
+ }
11061
11065
  if (!chainResult.success) throw new Error("Command failed");
11062
11066
  }
11063
11067
  async function routeCommand(argv, stdinCommands) {
@@ -11080,6 +11084,8 @@ async function routeCommand(argv, stdinCommands) {
11080
11084
  }
11081
11085
  const parsed = parseArgs(argv);
11082
11086
  const { positional, options } = parsed;
11087
+ const command = positional[0];
11088
+ const cmdArgs = positional.slice(1);
11083
11089
  const mode = options.json ? "json" : options.yaml ? "yaml" : "text";
11084
11090
  const sessionName = options.session || process.env.XBROWSER_SESSION || "default";
11085
11091
  const cdpEndpoint = options.cdp;
@@ -11091,8 +11097,6 @@ async function routeCommand(argv, stdinCommands) {
11091
11097
  showMainHelp();
11092
11098
  return;
11093
11099
  }
11094
- const command = positional[0];
11095
- const cmdArgs = positional.slice(1);
11096
11100
  if ((options.help || options.h) && positional.length > 0) {
11097
11101
  const loader = await getPluginLoader();
11098
11102
  const internalLoader = loader.getCore().loader;
@@ -11307,10 +11311,30 @@ Run "xbrowser ${command} --help" to see available commands.`
11307
11311
  const subCmdIdx = pluginNameIdx >= 0 ? argv.indexOf(subCommand, pluginNameIdx + 1) : -1;
11308
11312
  const rawPluginArgs = subCmdIdx >= 0 ? argv.slice(subCmdIdx + 1) : [];
11309
11313
  const params = parsePluginParams(rawPluginArgs, cmdEntry.parameters);
11314
+ if (cmdEntry.parameters) {
11315
+ const schemaAny = cmdEntry.parameters;
11316
+ const def = schemaAny._def;
11317
+ const shapeOrFn = def?.shape ?? schemaAny.shape;
11318
+ const shapeObj = typeof shapeOrFn === "function" ? shapeOrFn() : shapeOrFn;
11319
+ if (shapeObj && typeof shapeObj === "object") {
11320
+ const knownKeys = new Set(Object.keys(shapeObj));
11321
+ knownKeys.add("_target");
11322
+ for (const gk of KNOWN_GLOBAL_OPTIONS) knownKeys.add(gk.replace(/-([a-z])/g, (_, c) => c.toUpperCase()));
11323
+ const unknownKeys = Object.keys(params).filter((k) => !knownKeys.has(k));
11324
+ if (unknownKeys.length > 0) {
11325
+ const unknown = unknownKeys.map((k) => `--${k.replace(/([A-Z])/g, "-$1").toLowerCase()}`).join(", ");
11326
+ outputError(
11327
+ `Unknown parameter: ${unknown}
11328
+ Run "xbrowser ${command} ${subCommand} --help" to see available parameters.`
11329
+ );
11330
+ return;
11331
+ }
11332
+ }
11333
+ }
11310
11334
  if (options.target && !params._target) {
11311
11335
  params._target = options.target;
11312
11336
  }
11313
- const needsBrowser = cmdEntry.scope === "page";
11337
+ const needsBrowser = cmdEntry.scope === "page" || cmdEntry.scope === "browser";
11314
11338
  if (needsBrowser && !process.env.XBROWSER_DAEMON_WORKER) {
11315
11339
  const { forwardExec } = await import("./daemon-client-3IJD6X4B.js");
11316
11340
  const userTimeout = typeof params.timeout === "number" && params.timeout > 0 ? params.timeout * 1e3 + 3e4 : void 0;
@@ -11360,38 +11384,46 @@ Run "xbrowser ${command} --help" to see available commands.`
11360
11384
  }
11361
11385
  };
11362
11386
  try {
11387
+ const cmdStart = Date.now();
11388
+ const cmdHooks = await loadHooks();
11389
+ if (cmdHooks.length > 0 && session?.page) {
11390
+ await Promise.all(cmdHooks.map((h) => h.onBeforeCommand?.({ page: session.page, command: `${command} ${subCommand}`, params })));
11391
+ }
11363
11392
  const result = await cmdEntry.handler(params, ctx);
11393
+ const hookOutputs = [];
11394
+ if (cmdHooks.length > 0 && session?.page) {
11395
+ for (const h of cmdHooks) {
11396
+ const output = await h.onAfterCommand?.({ page: session.page, command: `${command} ${subCommand}`, params, result, duration: Date.now() - cmdStart });
11397
+ if (output) hookOutputs.push({ _hook: h.name, ...output });
11398
+ }
11399
+ }
11364
11400
  if (session && result && result.data) {
11365
11401
  const convUrl = result.data.conversationUrl;
11366
11402
  if (convUrl) {
11367
11403
  saveSessionDiskMeta(sessionName, { conversationUrl: convUrl, cdpEndpoint });
11368
11404
  }
11369
11405
  }
11370
- if (isCommandResult2(result)) {
11371
- if (mode === "json" || mode === "yaml") {
11372
- console.log(outputFormatter2.format(result.data, { mode, color: false, emoji: false }));
11373
- if (result.tips?.length) {
11374
- for (const tip of result.tips) console.error(`\u{1F4A1} ${tip}`);
11375
- }
11376
- } else {
11377
- console.log(outputFormatter2.format(result.data, { mode: "text", color: true, emoji: true }));
11378
- if (result.tips?.length) {
11379
- for (const tip of result.tips) console.log(` \u{1F4A1} ${tip}`);
11380
- }
11406
+ const outputData = isCommandResult2(result) ? result.data : result && typeof result === "object" ? result.data ?? result : result;
11407
+ const tips = isCommandResult2(result) ? result.tips : result && typeof result === "object" ? result.tips : void 0;
11408
+ if (mode === "json" || mode === "yaml") {
11409
+ const finalOutput = {
11410
+ data: outputData
11411
+ };
11412
+ if (hookOutputs.length > 0) {
11413
+ finalOutput.hooks = hookOutputs;
11381
11414
  }
11382
- } else if (result && typeof result === "object") {
11383
- const obj = result;
11384
- if (mode === "json" || mode === "yaml") {
11385
- console.log(outputFormatter2.format(obj.data ?? obj, { mode, color: false, emoji: false }));
11386
- const tips = obj.tips;
11387
- if (tips?.length) {
11388
- for (const tip of tips) console.error(`\u{1F4A1} ${tip}`);
11389
- }
11390
- } else {
11391
- if (obj.data) outputResult(obj.data, mode);
11392
- const tips = obj.tips;
11393
- if (tips?.length) {
11394
- for (const tip of tips) console.log(` \u{1F4A1} ${tip}`);
11415
+ console.log(outputFormatter2.format(finalOutput, { mode, color: false, emoji: false }));
11416
+ if (tips?.length) {
11417
+ for (const tip of tips) console.error(`\u{1F4A1} ${tip}`);
11418
+ }
11419
+ } else {
11420
+ console.log(outputFormatter2.format(outputData, { mode: "text", color: true, emoji: true }));
11421
+ if (tips?.length) {
11422
+ for (const tip of tips) console.log(` \u{1F4A1} ${tip}`);
11423
+ }
11424
+ if (hookOutputs.length > 0) {
11425
+ for (const ho of hookOutputs) {
11426
+ console.log(` \u{1F4F8} screenshot: ${ho.screenshot?.url || "captured"}`);
11395
11427
  }
11396
11428
  }
11397
11429
  }
@@ -12602,10 +12634,10 @@ var WSServer = class extends EventEmitter {
12602
12634
  }
12603
12635
  case "file_download": {
12604
12636
  try {
12605
- const { readFileSync: readFileSync17 } = await import("fs");
12637
+ const { readFileSync: readFileSync16 } = await import("fs");
12606
12638
  const { resolve: resolve16, basename: basename3 } = await import("path");
12607
12639
  const targetPath = resolve16(msg.path);
12608
- const data = readFileSync17(targetPath);
12640
+ const data = readFileSync16(targetPath);
12609
12641
  const base64 = data.toString("base64");
12610
12642
  const ext = targetPath.split(".").pop()?.toLowerCase() || "";
12611
12643
  const mimeMap = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xbrowser/cli",
3
- "version": "0.15.0",
3
+ "version": "0.16.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": {
@@ -82,7 +82,7 @@
82
82
  "prepare": "husky"
83
83
  },
84
84
  "dependencies": {
85
- "@dyyz1993/xcli-core": "^0.9.2",
85
+ "@dyyz1993/xcli-core": "^0.12.1",
86
86
  "@types/react-syntax-highlighter": "^15.5.13",
87
87
  "@types/turndown": "^5.0.6",
88
88
  "cheerio": "^1.2.0",