@xbrowser/cli 0.14.2 → 0.15.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
@@ -191,7 +191,18 @@ function parsePluginParams(args, schema, base = {}) {
191
191
  if (value === "true") params[key] = true;
192
192
  else if (value === "false") params[key] = false;
193
193
  else if (/^\d+$/.test(value)) params[key] = parseInt(value, 10);
194
- else params[key] = value;
194
+ else {
195
+ try {
196
+ const parsed = JSON.parse(value);
197
+ if (Array.isArray(parsed) || typeof parsed === "object" && parsed !== null) {
198
+ params[key] = parsed;
199
+ } else {
200
+ params[key] = value;
201
+ }
202
+ } catch {
203
+ params[key] = value;
204
+ }
205
+ }
195
206
  i++;
196
207
  } else {
197
208
  params[key] = true;
@@ -6535,8 +6546,48 @@ async function loadHooks() {
6535
6546
  // src/executor.ts
6536
6547
  import { homedir as homedir3 } from "os";
6537
6548
  import { join as join3 } from "path";
6549
+ import { existsSync as existsSync6, readFileSync as readFileSync9, writeFileSync as writeFileSync4, mkdirSync as mkdirSync3 } from "fs";
6538
6550
  var NAVIGATION_COMMANDS = /* @__PURE__ */ new Set(["goto", "back", "forward", "refresh"]);
6539
6551
  var snapshotHintShown = /* @__PURE__ */ new WeakSet();
6552
+ var STORAGE_DIR = join3(homedir3(), ".xbrowser", "storage");
6553
+ var storageCache = /* @__PURE__ */ new Map();
6554
+ function getPluginStorage(pluginName) {
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
+ });
6588
+ }
6589
+ return storageCache.get(pluginName);
6590
+ }
6540
6591
  var archiveInitialized = false;
6541
6592
  function ensureArchiveInit() {
6542
6593
  if (!archiveInitialized) {
@@ -6647,16 +6698,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
6647
6698
  args: [],
6648
6699
  options: {},
6649
6700
  cwd: process.cwd(),
6650
- storage: {
6651
- get: async () => null,
6652
- set: async () => {
6653
- },
6654
- delete: async () => {
6655
- },
6656
- clear: async () => {
6657
- },
6658
- keys: async () => []
6659
- },
6701
+ storage: getPluginStorage(commandName),
6660
6702
  output: {
6661
6703
  mode: "text",
6662
6704
  showTips: false,
@@ -6879,16 +6921,7 @@ async function executeChain(input, options) {
6879
6921
  browser: session.context.browser(),
6880
6922
  browserContext: session.context,
6881
6923
  sessionId: session.id,
6882
- storage: {
6883
- get: async (_key) => null,
6884
- set: async (_key, _value) => {
6885
- },
6886
- delete: async (_key) => {
6887
- },
6888
- clear: async () => {
6889
- },
6890
- keys: async () => []
6891
- },
6924
+ storage: getPluginStorage(cmdName),
6892
6925
  output: { mode: "text", showTips: false, color: false, emoji: false },
6893
6926
  error: (msg) => {
6894
6927
  throw new Error(msg);
@@ -6899,8 +6932,21 @@ async function executeChain(input, options) {
6899
6932
  };
6900
6933
  const start2 = Date.now();
6901
6934
  try {
6935
+ const hooks = await loadHooks();
6936
+ if (hooks.length > 0) {
6937
+ await Promise.all(hooks.map((h) => h.onBeforeCommand?.({ page: session.page, command: `${cmdName} ${subCommand}`, params: pluginParams })));
6938
+ }
6902
6939
  const raw = await cmdEntry.handler(pluginParams, pluginCtx);
6903
6940
  const duration2 = Date.now() - start2;
6941
+ let hookOutputs;
6942
+ if (hooks.length > 0) {
6943
+ const outputs = [];
6944
+ for (const h of hooks) {
6945
+ const output = await h.onAfterCommand?.({ page: session.page, command: `${cmdName} ${subCommand}`, params: pluginParams, result: raw, duration: duration2 });
6946
+ if (output) outputs.push({ _hook: h.name, ...output });
6947
+ }
6948
+ if (outputs.length > 0) hookOutputs = outputs;
6949
+ }
6904
6950
  const data = raw?.data ?? raw;
6905
6951
  recordArchive(session.id, sessionName, {
6906
6952
  step: results.length,
@@ -6915,7 +6961,8 @@ async function executeChain(input, options) {
6915
6961
  command: `${cmdName} ${subCommand}`,
6916
6962
  raw: cmdStr,
6917
6963
  ...ok25(data),
6918
- duration: duration2
6964
+ duration: duration2,
6965
+ ...hookOutputs ? { hookOutputs } : {}
6919
6966
  });
6920
6967
  if (type === "or") {
6921
6968
  return {
@@ -6979,7 +7026,8 @@ async function executeChain(input, options) {
6979
7026
  data: result.data,
6980
7027
  message: result.message,
6981
7028
  duration,
6982
- tips: result.tips
7029
+ tips: result.tips,
7030
+ ...result.hookOutputs ? { hookOutputs: result.hookOutputs } : {}
6983
7031
  };
6984
7032
  results.push(stepResult);
6985
7033
  if (type === "and" && !result.success) {
@@ -7285,26 +7333,26 @@ var configBuiltin = {
7285
7333
 
7286
7334
  // src/plugin/installer.ts
7287
7335
  import {
7288
- existsSync as existsSync12,
7336
+ existsSync as existsSync13,
7289
7337
  readdirSync as readdirSync3,
7290
- mkdirSync as mkdirSync7,
7338
+ mkdirSync as mkdirSync8,
7291
7339
  rmSync as rmSync7
7292
7340
  } from "fs";
7293
7341
  import { resolve as resolve15, basename as basename2 } from "path";
7294
7342
  import { homedir as homedir4 } from "os";
7295
7343
 
7296
7344
  // src/plugin/install-sources/local.ts
7297
- import { existsSync as existsSync7, cpSync as cpSync2, rmSync as rmSync2 } from "fs";
7345
+ import { existsSync as existsSync8, cpSync as cpSync2, rmSync as rmSync2 } from "fs";
7298
7346
  import { resolve as resolve10 } from "path";
7299
7347
 
7300
7348
  // src/plugin/install-utils.ts
7301
7349
  import {
7302
- existsSync as existsSync6,
7350
+ existsSync as existsSync7,
7303
7351
  readdirSync as readdirSync2,
7304
7352
  cpSync,
7305
7353
  rmSync,
7306
- mkdirSync as mkdirSync3,
7307
- readFileSync as readFileSync9,
7354
+ mkdirSync as mkdirSync4,
7355
+ readFileSync as readFileSync10,
7308
7356
  createWriteStream
7309
7357
  } from "fs";
7310
7358
  import { resolve as resolve9 } from "path";
@@ -7375,7 +7423,7 @@ async function downloadToFile(url, destPath) {
7375
7423
  await pipeline(nodeStream, createWriteStream(destPath));
7376
7424
  }
7377
7425
  function extractTarGz(tarballPath, targetDir) {
7378
- mkdirSync3(targetDir, { recursive: true });
7426
+ mkdirSync4(targetDir, { recursive: true });
7379
7427
  execSync7(`tar -xzf "${tarballPath}" -C "${targetDir}"`, { stdio: "pipe" });
7380
7428
  }
7381
7429
  function flattenPackageRoot(targetDir) {
@@ -7396,18 +7444,18 @@ function flattenPackageRoot(targetDir) {
7396
7444
  async function verifyPlugin(dir) {
7397
7445
  const warnings = [];
7398
7446
  const indexPath = resolve9(dir, "index.ts");
7399
- if (!existsSync6(indexPath)) {
7447
+ if (!existsSync7(indexPath)) {
7400
7448
  const indexJs = resolve9(dir, "index.js");
7401
- if (!existsSync6(indexJs)) {
7449
+ if (!existsSync7(indexJs)) {
7402
7450
  return { valid: false, error: "No index.ts or index.js entry point found", warnings };
7403
7451
  }
7404
7452
  }
7405
7453
  const pkgPath = resolve9(dir, "package.json");
7406
- if (!existsSync6(pkgPath)) {
7454
+ if (!existsSync7(pkgPath)) {
7407
7455
  warnings.push("No package.json found");
7408
7456
  } else {
7409
7457
  try {
7410
- const pkg2 = JSON.parse(readFileSync9(pkgPath, "utf-8"));
7458
+ const pkg2 = JSON.parse(readFileSync10(pkgPath, "utf-8"));
7411
7459
  if (!pkg2.xbrowser) {
7412
7460
  warnings.push("No xbrowser metadata in package.json");
7413
7461
  }
@@ -7427,7 +7475,7 @@ function safeCleanup(dir) {
7427
7475
  // src/plugin/install-sources/local.ts
7428
7476
  async function installFromLocal(source, name, targetDir) {
7429
7477
  const srcPath = resolve10(source);
7430
- if (!existsSync7(srcPath)) {
7478
+ if (!existsSync8(srcPath)) {
7431
7479
  throw new Error(`Local path does not exist: ${srcPath}`);
7432
7480
  }
7433
7481
  const tmpTarget = `${targetDir}-tmp-${Date.now()}`;
@@ -7440,7 +7488,7 @@ async function installFromLocal(source, name, targetDir) {
7440
7488
  safeCleanup(tmpTarget);
7441
7489
  throw new Error(`Invalid plugin: ${verify.error}`);
7442
7490
  }
7443
- if (existsSync7(targetDir)) {
7491
+ if (existsSync8(targetDir)) {
7444
7492
  rmSync2(targetDir, { recursive: true, force: true });
7445
7493
  }
7446
7494
  cpSync2(tmpTarget, targetDir, { recursive: true, force: true });
@@ -7460,7 +7508,7 @@ async function installFromLocal(source, name, targetDir) {
7460
7508
  }
7461
7509
 
7462
7510
  // src/plugin/install-sources/npm.ts
7463
- import { existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync10, writeFileSync as writeFileSync4, rmSync as rmSync3, cpSync as cpSync3 } from "fs";
7511
+ import { existsSync as existsSync9, mkdirSync as mkdirSync5, readFileSync as readFileSync11, writeFileSync as writeFileSync5, rmSync as rmSync3, cpSync as cpSync3 } from "fs";
7464
7512
  import { resolve as resolve11, join as join4 } from "path";
7465
7513
  import { tmpdir } from "os";
7466
7514
  async function installFromNpm(packageName, name, targetDir) {
@@ -7481,7 +7529,7 @@ async function installFromNpm(packageName, name, targetDir) {
7481
7529
  }
7482
7530
  const tarballUrl = versionMeta.dist.tarball;
7483
7531
  const tmpDir = join4(tmpdir(), `xbrowser-npm-${Date.now()}`);
7484
- mkdirSync4(tmpDir, { recursive: true });
7532
+ mkdirSync5(tmpDir, { recursive: true });
7485
7533
  let warnings = [];
7486
7534
  try {
7487
7535
  const tarballPath = join4(tmpDir, `${name}.tgz`);
@@ -7494,16 +7542,16 @@ async function installFromNpm(packageName, name, targetDir) {
7494
7542
  if (!verify.valid) {
7495
7543
  throw new Error(`Invalid npm plugin: ${verify.error}`);
7496
7544
  }
7497
- if (existsSync8(targetDir)) {
7545
+ if (existsSync9(targetDir)) {
7498
7546
  rmSync3(targetDir, { recursive: true, force: true });
7499
7547
  }
7500
7548
  cpSync3(extractDir, targetDir, { recursive: true, force: true });
7501
7549
  const pkgPath = resolve11(targetDir, "package.json");
7502
- if (existsSync8(pkgPath)) {
7503
- const pkg2 = JSON.parse(readFileSync10(pkgPath, "utf-8"));
7550
+ if (existsSync9(pkgPath)) {
7551
+ const pkg2 = JSON.parse(readFileSync11(pkgPath, "utf-8"));
7504
7552
  if (!pkg2._npmSource) {
7505
7553
  pkg2._npmSource = { name: packageName, version: latestVersion };
7506
- writeFileSync4(pkgPath, JSON.stringify(pkg2, null, 2));
7554
+ writeFileSync5(pkgPath, JSON.stringify(pkg2, null, 2));
7507
7555
  }
7508
7556
  }
7509
7557
  } finally {
@@ -7520,7 +7568,7 @@ async function installFromNpm(packageName, name, targetDir) {
7520
7568
  }
7521
7569
 
7522
7570
  // src/plugin/install-sources/git.ts
7523
- import { existsSync as existsSync9, readFileSync as readFileSync11, writeFileSync as writeFileSync5, rmSync as rmSync4, cpSync as cpSync4 } from "fs";
7571
+ import { existsSync as existsSync10, readFileSync as readFileSync12, writeFileSync as writeFileSync6, rmSync as rmSync4, cpSync as cpSync4 } from "fs";
7524
7572
  import { resolve as resolve12, join as join5 } from "path";
7525
7573
  import { tmpdir as tmpdir2 } from "os";
7526
7574
  import { execSync as execSync8 } from "child_process";
@@ -7534,17 +7582,17 @@ async function installFromGit(gitUrl, name, targetDir) {
7534
7582
  if (!verify.valid) {
7535
7583
  throw new Error(`Invalid git plugin: ${verify.error}`);
7536
7584
  }
7537
- if (existsSync9(targetDir)) {
7585
+ if (existsSync10(targetDir)) {
7538
7586
  rmSync4(targetDir, { recursive: true, force: true });
7539
7587
  }
7540
7588
  cpSync4(tmpDir, targetDir, { recursive: true, force: true });
7541
7589
  rmSync4(resolve12(targetDir, ".git"), { recursive: true, force: true });
7542
7590
  const pkgPath = resolve12(targetDir, "package.json");
7543
- if (existsSync9(pkgPath)) {
7544
- const pkg2 = JSON.parse(readFileSync11(pkgPath, "utf-8"));
7591
+ if (existsSync10(pkgPath)) {
7592
+ const pkg2 = JSON.parse(readFileSync12(pkgPath, "utf-8"));
7545
7593
  if (!pkg2._gitSource) {
7546
7594
  pkg2._gitSource = { url: gitUrl };
7547
- writeFileSync5(pkgPath, JSON.stringify(pkg2, null, 2));
7595
+ writeFileSync6(pkgPath, JSON.stringify(pkg2, null, 2));
7548
7596
  }
7549
7597
  }
7550
7598
  } finally {
@@ -7561,12 +7609,12 @@ async function installFromGit(gitUrl, name, targetDir) {
7561
7609
  }
7562
7610
 
7563
7611
  // src/plugin/install-sources/url.ts
7564
- import { existsSync as existsSync10, readFileSync as readFileSync12, writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, rmSync as rmSync5, cpSync as cpSync5 } from "fs";
7612
+ import { existsSync as existsSync11, readFileSync as readFileSync13, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, rmSync as rmSync5, cpSync as cpSync5 } from "fs";
7565
7613
  import { resolve as resolve13, join as join6, basename } from "path";
7566
7614
  import { tmpdir as tmpdir3 } from "os";
7567
7615
  async function installFromUrl(url, name, targetDir) {
7568
7616
  const tmpDir = join6(tmpdir3(), `xbrowser-url-${Date.now()}`);
7569
- mkdirSync5(tmpDir, { recursive: true });
7617
+ mkdirSync6(tmpDir, { recursive: true });
7570
7618
  let warnings = [];
7571
7619
  try {
7572
7620
  const fileName = basename(new URL(url).pathname) || "plugin.tar.gz";
@@ -7580,16 +7628,16 @@ async function installFromUrl(url, name, targetDir) {
7580
7628
  if (!verify.valid) {
7581
7629
  throw new Error(`Invalid plugin from URL: ${verify.error}`);
7582
7630
  }
7583
- if (existsSync10(targetDir)) {
7631
+ if (existsSync11(targetDir)) {
7584
7632
  rmSync5(targetDir, { recursive: true, force: true });
7585
7633
  }
7586
7634
  cpSync5(extractDir, targetDir, { recursive: true, force: true });
7587
7635
  const pkgPath = resolve13(targetDir, "package.json");
7588
- if (existsSync10(pkgPath)) {
7589
- const pkg2 = JSON.parse(readFileSync12(pkgPath, "utf-8"));
7636
+ if (existsSync11(pkgPath)) {
7637
+ const pkg2 = JSON.parse(readFileSync13(pkgPath, "utf-8"));
7590
7638
  if (!pkg2._urlSource) {
7591
7639
  pkg2._urlSource = { url };
7592
- writeFileSync6(pkgPath, JSON.stringify(pkg2, null, 2));
7640
+ writeFileSync7(pkgPath, JSON.stringify(pkg2, null, 2));
7593
7641
  }
7594
7642
  }
7595
7643
  } finally {
@@ -7607,10 +7655,10 @@ async function installFromUrl(url, name, targetDir) {
7607
7655
 
7608
7656
  // src/plugin/install-sources/marketplace.ts
7609
7657
  import {
7610
- existsSync as existsSync11,
7611
- mkdirSync as mkdirSync6,
7612
- writeFileSync as writeFileSync7,
7613
- readFileSync as readFileSync13,
7658
+ existsSync as existsSync12,
7659
+ mkdirSync as mkdirSync7,
7660
+ writeFileSync as writeFileSync8,
7661
+ readFileSync as readFileSync14,
7614
7662
  rmSync as rmSync6,
7615
7663
  cpSync as cpSync6
7616
7664
  } from "fs";
@@ -7632,12 +7680,12 @@ async function installFromMarketplace(pluginsDir, slug, options) {
7632
7680
  const plugin = detailData.data;
7633
7681
  const name = options?.name || String(plugin.slug || slug);
7634
7682
  const targetDir = resolve14(pluginsDir, name);
7635
- if (existsSync11(targetDir) && !options?.force) {
7683
+ if (existsSync12(targetDir) && !options?.force) {
7636
7684
  throw new Error(`Plugin "${name}" already exists. Use --force to overwrite.`);
7637
7685
  }
7638
- mkdirSync6(targetDir, { recursive: true });
7686
+ mkdirSync7(targetDir, { recursive: true });
7639
7687
  const tmpDir = join7(tmpdir4(), `xbrowser-marketplace-${Date.now()}`);
7640
- mkdirSync6(tmpDir, { recursive: true });
7688
+ mkdirSync7(tmpDir, { recursive: true });
7641
7689
  const realSlug = String(plugin.slug || slug);
7642
7690
  try {
7643
7691
  await downloadAndExtractMarketplaceTarball(baseUrl, realSlug, tmpDir, targetDir);
@@ -7667,14 +7715,14 @@ function isManifestArray(data) {
7667
7715
  return Array.isArray(data) && data.length > 0 && typeof data[0].path === "string" && typeof data[0].content === "string";
7668
7716
  }
7669
7717
  function extractManifestToDir(manifest, targetDir) {
7670
- if (existsSync11(targetDir)) {
7718
+ if (existsSync12(targetDir)) {
7671
7719
  rmSync6(targetDir, { recursive: true, force: true });
7672
7720
  }
7673
- mkdirSync6(targetDir, { recursive: true });
7721
+ mkdirSync7(targetDir, { recursive: true });
7674
7722
  for (const file of manifest) {
7675
7723
  const filePath = resolve14(targetDir, file.path);
7676
- mkdirSync6(dirname2(filePath), { recursive: true });
7677
- writeFileSync7(filePath, Buffer.from(file.content, "base64"));
7724
+ mkdirSync7(dirname2(filePath), { recursive: true });
7725
+ writeFileSync8(filePath, Buffer.from(file.content, "base64"));
7678
7726
  }
7679
7727
  }
7680
7728
  function tryParseAsGzippedManifest(buffer) {
@@ -7701,7 +7749,7 @@ async function downloadAndExtractMarketplaceTarball(baseUrl, slug, tmpDir, targe
7701
7749
  const redirectUrl = tarballRes.headers.get("location");
7702
7750
  const tarballPath = join7(tmpDir, `${slug}.tar.gz`);
7703
7751
  await downloadToFile(redirectUrl, tarballPath);
7704
- const buffer = readFileSync13(tarballPath);
7752
+ const buffer = readFileSync14(tarballPath);
7705
7753
  const manifest = tryParseAsGzippedManifest(buffer);
7706
7754
  if (manifest) {
7707
7755
  extractManifestToDir(manifest, targetDir);
@@ -7710,7 +7758,7 @@ async function downloadAndExtractMarketplaceTarball(baseUrl, slug, tmpDir, targe
7710
7758
  const extractDir = join7(tmpDir, "extracted");
7711
7759
  extractTarGz(tarballPath, extractDir);
7712
7760
  flattenPackageRoot(extractDir);
7713
- if (existsSync11(targetDir)) {
7761
+ if (existsSync12(targetDir)) {
7714
7762
  rmSync6(targetDir, { recursive: true, force: true });
7715
7763
  }
7716
7764
  cpSync6(extractDir, targetDir, { recursive: true, force: true });
@@ -7722,12 +7770,12 @@ async function downloadAndExtractMarketplaceTarball(baseUrl, slug, tmpDir, targe
7722
7770
  return;
7723
7771
  }
7724
7772
  const tarballPath = join7(tmpDir, `${slug}.tar.gz`);
7725
- writeFileSync7(tarballPath, buffer);
7773
+ writeFileSync8(tarballPath, buffer);
7726
7774
  try {
7727
7775
  const extractDir = join7(tmpDir, "extracted");
7728
7776
  extractTarGz(tarballPath, extractDir);
7729
7777
  flattenPackageRoot(extractDir);
7730
- if (existsSync11(targetDir)) {
7778
+ if (existsSync12(targetDir)) {
7731
7779
  rmSync6(targetDir, { recursive: true, force: true });
7732
7780
  }
7733
7781
  cpSync6(extractDir, targetDir, { recursive: true, force: true });
@@ -7763,24 +7811,24 @@ function writeMarketplacePackageJson(plugin, slug, name, baseUrl, targetDir) {
7763
7811
  }
7764
7812
  };
7765
7813
  const pkgPath = resolve14(targetDir, "package.json");
7766
- if (!existsSync11(pkgPath)) {
7767
- writeFileSync7(pkgPath, JSON.stringify(packageJson, null, 2));
7814
+ if (!existsSync12(pkgPath)) {
7815
+ writeFileSync8(pkgPath, JSON.stringify(packageJson, null, 2));
7768
7816
  } else {
7769
7817
  try {
7770
- const existing = JSON.parse(readFileSync13(pkgPath, "utf-8"));
7818
+ const existing = JSON.parse(readFileSync14(pkgPath, "utf-8"));
7771
7819
  const merged = {
7772
7820
  ...existing,
7773
7821
  xbrowser: { ...existing.xbrowser, ...packageJson.xbrowser },
7774
7822
  _marketplace: packageJson._marketplace
7775
7823
  };
7776
- writeFileSync7(pkgPath, JSON.stringify(merged, null, 2));
7824
+ writeFileSync8(pkgPath, JSON.stringify(merged, null, 2));
7777
7825
  } catch {
7778
- writeFileSync7(pkgPath, JSON.stringify(packageJson, null, 2));
7826
+ writeFileSync8(pkgPath, JSON.stringify(packageJson, null, 2));
7779
7827
  }
7780
7828
  }
7781
7829
  }
7782
7830
  function ensureIndexFile(plugin, name, targetDir) {
7783
- if (!existsSync11(resolve14(targetDir, "index.ts")) && !existsSync11(resolve14(targetDir, "index.js"))) {
7831
+ if (!existsSync12(resolve14(targetDir, "index.ts")) && !existsSync12(resolve14(targetDir, "index.js"))) {
7784
7832
  const commands = plugin.commands || [];
7785
7833
  const commandHandlers = commands.length > 0 ? commands.map((cmd) => {
7786
7834
  return [
@@ -7795,7 +7843,7 @@ function ensureIndexFile(plugin, name, targetDir) {
7795
7843
  ` handler: async () => ({ data: { message: 'Hello from ${name}!' }, tips: [] }),`,
7796
7844
  ` });`
7797
7845
  ].join("\n");
7798
- writeFileSync7(
7846
+ writeFileSync8(
7799
7847
  resolve14(targetDir, "index.ts"),
7800
7848
  [
7801
7849
  `import type { XCLIAPI } from '@dyyz1993/xcli-core';`,
@@ -7834,10 +7882,10 @@ var PluginInstaller = class {
7834
7882
  const type = this.detectSourceType(source);
7835
7883
  const name = options?.name || this.deriveName(source, type);
7836
7884
  const targetDir = resolve15(this.pluginsDir, name);
7837
- if (existsSync12(targetDir) && !options?.force) {
7885
+ if (existsSync13(targetDir) && !options?.force) {
7838
7886
  throw new Error(`Plugin "${name}" already exists. Use --force to overwrite.`);
7839
7887
  }
7840
- mkdirSync7(targetDir, { recursive: true });
7888
+ mkdirSync8(targetDir, { recursive: true });
7841
7889
  const resolvedSource = type === "npm" ? await resolveNpmPackageWithFallback(source) : source;
7842
7890
  switch (type) {
7843
7891
  case "local":
@@ -7895,7 +7943,7 @@ var PluginInstaller = class {
7895
7943
  */
7896
7944
  async uninstall(name) {
7897
7945
  const targetDir = resolve15(this.pluginsDir, name);
7898
- if (!existsSync12(targetDir)) {
7946
+ if (!existsSync13(targetDir)) {
7899
7947
  throw new Error(`Plugin "${name}" not found`);
7900
7948
  }
7901
7949
  rmSync7(targetDir, { recursive: true, force: true });
@@ -7906,7 +7954,7 @@ var PluginInstaller = class {
7906
7954
  * @returns Array of installed plugin information.
7907
7955
  */
7908
7956
  async list(_options) {
7909
- if (!existsSync12(this.pluginsDir)) return [];
7957
+ if (!existsSync13(this.pluginsDir)) return [];
7910
7958
  const entries = readdirSync3(this.pluginsDir, { withFileTypes: true });
7911
7959
  const plugins = [];
7912
7960
  for (const entry of entries) {
@@ -7914,7 +7962,7 @@ var PluginInstaller = class {
7914
7962
  const pluginPath = resolve15(this.pluginsDir, entry.name);
7915
7963
  const indexPath = resolve15(pluginPath, "index.ts");
7916
7964
  const indexJsPath = resolve15(pluginPath, "index.js");
7917
- if (!existsSync12(indexPath) && !existsSync12(indexJsPath)) continue;
7965
+ if (!existsSync13(indexPath) && !existsSync13(indexJsPath)) continue;
7918
7966
  const metadata = PluginMetadataParser.parseFromPackageJson(pluginPath);
7919
7967
  let source = "local";
7920
7968
  const pkg2 = readJsonFile(resolve15(pluginPath, "package.json"), {});
@@ -7941,10 +7989,10 @@ var PluginInstaller = class {
7941
7989
  }
7942
7990
  if (source.startsWith("file://")) {
7943
7991
  const filePath = decodeURIComponent(new URL(source).pathname);
7944
- if (existsSync12(filePath)) return "url";
7992
+ if (existsSync13(filePath)) return "url";
7945
7993
  }
7946
7994
  if (source.endsWith(".git") || source.includes("github.com/")) return "git";
7947
- if (existsSync12(resolve15(source))) return "local";
7995
+ if (existsSync13(resolve15(source))) return "local";
7948
7996
  return "npm";
7949
7997
  }
7950
7998
  deriveName(source, type) {
@@ -9973,7 +10021,7 @@ async function handleFilter(args, _mode) {
9973
10021
 
9974
10022
  // src/stdin.ts
9975
10023
  import { createInterface } from "readline";
9976
- import { readFileSync as readFileSync14 } from "fs";
10024
+ import { readFileSync as readFileSync15 } from "fs";
9977
10025
  async function readStdin2() {
9978
10026
  if (process.stdin.isTTY) return [];
9979
10027
  const lines = [];
@@ -9987,7 +10035,7 @@ async function readStdin2() {
9987
10035
  return lines;
9988
10036
  }
9989
10037
  function readCommandFile(filePath) {
9990
- const content = readFileSync14(filePath, "utf-8");
10038
+ const content = readFileSync15(filePath, "utf-8");
9991
10039
  return content.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#"));
9992
10040
  }
9993
10041
 
@@ -11255,11 +11303,14 @@ Run "xbrowser ${command} --help" to see available commands.`
11255
11303
  showCommandHelp(command, cmdEntry, { description: site.config.description, name: site.name, url: site.url }, mode);
11256
11304
  return;
11257
11305
  }
11258
- const params = parsePluginParams(cmdArgsForPlugin, cmdEntry.parameters, options);
11306
+ const pluginNameIdx = argv.indexOf(command);
11307
+ const subCmdIdx = pluginNameIdx >= 0 ? argv.indexOf(subCommand, pluginNameIdx + 1) : -1;
11308
+ const rawPluginArgs = subCmdIdx >= 0 ? argv.slice(subCmdIdx + 1) : [];
11309
+ const params = parsePluginParams(rawPluginArgs, cmdEntry.parameters);
11259
11310
  if (options.target && !params._target) {
11260
11311
  params._target = options.target;
11261
11312
  }
11262
- const needsBrowser = cmdEntry.scope !== "global";
11313
+ const needsBrowser = cmdEntry.scope === "page";
11263
11314
  if (needsBrowser && !process.env.XBROWSER_DAEMON_WORKER) {
11264
11315
  const { forwardExec } = await import("./daemon-client-3IJD6X4B.js");
11265
11316
  const userTimeout = typeof params.timeout === "number" && params.timeout > 0 ? params.timeout * 1e3 + 3e4 : void 0;
@@ -11296,16 +11347,7 @@ Run "xbrowser ${command} --help" to see available commands.`
11296
11347
  browser: needsBrowser ? session.context.browser() : null,
11297
11348
  browserContext: needsBrowser ? session.context : null,
11298
11349
  sessionId: needsBrowser ? session.id : "",
11299
- storage: {
11300
- get: async (_key) => null,
11301
- set: async (_key, _value) => {
11302
- },
11303
- delete: async (_key) => {
11304
- },
11305
- clear: async () => {
11306
- },
11307
- keys: async () => []
11308
- },
11350
+ storage: getPluginStorage(command),
11309
11351
  output: { mode, showTips: true, color: true, emoji: true },
11310
11352
  error: (msg) => {
11311
11353
  outputError(msg);
@@ -12560,10 +12602,10 @@ var WSServer = class extends EventEmitter {
12560
12602
  }
12561
12603
  case "file_download": {
12562
12604
  try {
12563
- const { readFileSync: readFileSync16 } = await import("fs");
12605
+ const { readFileSync: readFileSync17 } = await import("fs");
12564
12606
  const { resolve: resolve16, basename: basename3 } = await import("path");
12565
12607
  const targetPath = resolve16(msg.path);
12566
- const data = readFileSync16(targetPath);
12608
+ const data = readFileSync17(targetPath);
12567
12609
  const base64 = data.toString("base64");
12568
12610
  const ext = targetPath.split(".").pop()?.toLowerCase() || "";
12569
12611
  const mimeMap = {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@xbrowser/cli",
3
- "version": "0.14.2",
4
- "description": "A self-contained browser automation CLI tool navigate, click, fill forms, extract data, record & replay sessions via YAML scripts",
3
+ "version": "0.15.0",
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": {
7
7
  "xbrowser": "dist/cli.js"
@@ -27,21 +27,37 @@
27
27
  "record-replay",
28
28
  "browser automation",
29
29
  "web scraping",
30
+ "web-scraping",
30
31
  "playwright alternative",
32
+ "playwright-alternative",
31
33
  "puppeteer alternative",
34
+ "puppeteer-alternative",
32
35
  "selenium alternative",
36
+ "selenium-alternative",
33
37
  "web crawler",
38
+ "web-crawler",
34
39
  "headless browser",
40
+ "headless-browser",
41
+ "headless-chrome",
35
42
  "scrape",
36
43
  "crawl",
44
+ "crawler",
45
+ "scraping",
37
46
  "cli tool",
38
47
  "command line",
48
+ "command-line",
39
49
  "browser cli",
50
+ "browser-cli",
40
51
  "anti-detection",
41
52
  "record replay",
42
53
  "seo tool",
54
+ "seo",
55
+ "search-engine",
43
56
  "ai agent",
44
- "browser plugin"
57
+ "ai-agent",
58
+ "browser plugin",
59
+ "browser-testing",
60
+ "chrome-devtools-protocol"
45
61
  ],
46
62
  "author": "dyyz1993",
47
63
  "license": "MIT",