@xbrowser/cli 1.8.5 → 1.8.6

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.
@@ -20,7 +20,7 @@ import {
20
20
  saveSessionDiskMeta,
21
21
  setActivePage,
22
22
  touchSession
23
- } from "./chunk-TAHJCCME.js";
23
+ } from "./chunk-HXLMMSU3.js";
24
24
  import "./chunk-VJNMAWPZ.js";
25
25
  import "./chunk-TNEN6VQ2.js";
26
26
  import "./chunk-GDKLH7ZY.js";
@@ -20,7 +20,7 @@ import {
20
20
  saveSessionDiskMeta,
21
21
  setActivePage,
22
22
  touchSession
23
- } from "./chunk-7EOQV6ZW.js";
23
+ } from "./chunk-CJJ564VK.js";
24
24
  import "./chunk-VJNMAWPZ.js";
25
25
  import "./chunk-TNEN6VQ2.js";
26
26
  import "./chunk-GDKLH7ZY.js";
@@ -20,7 +20,7 @@ import {
20
20
  saveSessionDiskMeta,
21
21
  setActivePage,
22
22
  touchSession
23
- } from "./chunk-U25ZKXBJ.js";
23
+ } from "./chunk-SJLRCE6N.js";
24
24
  import "./chunk-TNEN6VQ2.js";
25
25
  import "./chunk-GDKLH7ZY.js";
26
26
  import "./chunk-KFQGP6VL.js";
@@ -656,7 +656,7 @@ async function closeSessionByName(name) {
656
656
  } catch {
657
657
  }
658
658
  try {
659
- const { SessionRecorder } = await import("./session-recorder-H2WPBCSW.js");
659
+ const { SessionRecorder } = await import("./session-recorder-33UKWCIR.js");
660
660
  SessionRecorder.cleanup(session.name);
661
661
  } catch {
662
662
  }
@@ -1891,15 +1891,37 @@ var SessionRecorder = class _SessionRecorder {
1891
1891
  }
1892
1892
  // ==================== Private ====================
1893
1893
  async injectActionScript(page) {
1894
- try {
1895
- await page.evaluate(getSelectorGeneratorScript());
1896
- await page.evaluate(ACTION_SIGNAL_SCRIPT);
1897
- await page.evaluate(`window.__xb_action_script_src = ${JSON.stringify(ACTION_SIGNAL_SCRIPT)};`);
1898
- } catch {
1894
+ let success = false;
1895
+ const maxAttempts = 3;
1896
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
1897
+ try {
1898
+ await page.evaluate(getSelectorGeneratorScript());
1899
+ await page.evaluate(ACTION_SIGNAL_SCRIPT);
1900
+ await page.evaluate(`window.__xb_action_script_src = ${JSON.stringify(ACTION_SIGNAL_SCRIPT)};`);
1901
+ const injected = await page.evaluate(`(function() {
1902
+ try { return !!window.__xb_action_signal; } catch(e) { return false; }
1903
+ })()`).catch(() => false);
1904
+ if (injected) {
1905
+ success = true;
1906
+ break;
1907
+ }
1908
+ console.error(`[recorder] Action signal injection verification failed (attempt ${attempt}/${maxAttempts})`);
1909
+ } catch (e) {
1910
+ const msg = e instanceof Error ? e.message : String(e);
1911
+ console.error(`[recorder] Action signal injection error (attempt ${attempt}/${maxAttempts}): ${msg}`);
1912
+ }
1913
+ if (attempt < maxAttempts) {
1914
+ await new Promise((r) => setTimeout(r, 1e3));
1915
+ }
1916
+ }
1917
+ if (!success) {
1918
+ console.error(`[recorder] WARNING: Action signal injection FAILED after ${maxAttempts} attempts. User actions will NOT be recorded. Check that the page supports Runtime.evaluate.`);
1899
1919
  }
1900
1920
  try {
1901
1921
  await page.evaluate(CHECKPOINT_OVERLAY_SCRIPT);
1902
- } catch {
1922
+ } catch (e) {
1923
+ const msg = e instanceof Error ? e.message : String(e);
1924
+ console.error(`[recorder] Checkpoint overlay injection failed (non-fatal): ${msg}`);
1903
1925
  }
1904
1926
  try {
1905
1927
  await page.evaluate(`
@@ -2051,7 +2073,11 @@ var SessionRecorder = class _SessionRecorder {
2051
2073
  await this.injectActionScript(page);
2052
2074
  return;
2053
2075
  }
2054
- } catch {
2076
+ } catch (e) {
2077
+ const msg = e instanceof Error ? e.message : String(e);
2078
+ if (attempt < 4) {
2079
+ console.error(`[recorder] injectActionScript retry ${attempt + 1}/5 failed: ${msg}`);
2080
+ }
2055
2081
  }
2056
2082
  await new Promise((r) => setTimeout(r, 1e3));
2057
2083
  }
@@ -2071,7 +2097,9 @@ var SessionRecorder = class _SessionRecorder {
2071
2097
  if (url && url !== "about:blank" && !url.startsWith("chrome")) {
2072
2098
  try {
2073
2099
  await this.injectActionScript(page);
2074
- } catch {
2100
+ } catch (e) {
2101
+ const msg = e instanceof Error ? e.message : String(e);
2102
+ console.error(`[recorder] injectActionScript after navigation failed: ${msg}`);
2075
2103
  }
2076
2104
  }
2077
2105
  });
@@ -2021,7 +2021,7 @@ async function closeSessionByName(name) {
2021
2021
  } catch {
2022
2022
  }
2023
2023
  try {
2024
- const { SessionRecorder } = await import("./session-recorder-CV5EJSDY.js");
2024
+ const { SessionRecorder } = await import("./session-recorder-BMIJ3ZJM.js");
2025
2025
  SessionRecorder.cleanup(session.name);
2026
2026
  } catch {
2027
2027
  }
@@ -1494,15 +1494,37 @@ var SessionRecorder = class _SessionRecorder {
1494
1494
  }
1495
1495
  // ==================== Private ====================
1496
1496
  async injectActionScript(page) {
1497
- try {
1498
- await page.evaluate(getSelectorGeneratorScript());
1499
- await page.evaluate(ACTION_SIGNAL_SCRIPT);
1500
- await page.evaluate(`window.__xb_action_script_src = ${JSON.stringify(ACTION_SIGNAL_SCRIPT)};`);
1501
- } catch {
1497
+ let success = false;
1498
+ const maxAttempts = 3;
1499
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
1500
+ try {
1501
+ await page.evaluate(getSelectorGeneratorScript());
1502
+ await page.evaluate(ACTION_SIGNAL_SCRIPT);
1503
+ await page.evaluate(`window.__xb_action_script_src = ${JSON.stringify(ACTION_SIGNAL_SCRIPT)};`);
1504
+ const injected = await page.evaluate(`(function() {
1505
+ try { return !!window.__xb_action_signal; } catch(e) { return false; }
1506
+ })()`).catch(() => false);
1507
+ if (injected) {
1508
+ success = true;
1509
+ break;
1510
+ }
1511
+ console.error(`[recorder] Action signal injection verification failed (attempt ${attempt}/${maxAttempts})`);
1512
+ } catch (e) {
1513
+ const msg = e instanceof Error ? e.message : String(e);
1514
+ console.error(`[recorder] Action signal injection error (attempt ${attempt}/${maxAttempts}): ${msg}`);
1515
+ }
1516
+ if (attempt < maxAttempts) {
1517
+ await new Promise((r) => setTimeout(r, 1e3));
1518
+ }
1519
+ }
1520
+ if (!success) {
1521
+ console.error(`[recorder] WARNING: Action signal injection FAILED after ${maxAttempts} attempts. User actions will NOT be recorded. Check that the page supports Runtime.evaluate.`);
1502
1522
  }
1503
1523
  try {
1504
1524
  await page.evaluate(CHECKPOINT_OVERLAY_SCRIPT);
1505
- } catch {
1525
+ } catch (e) {
1526
+ const msg = e instanceof Error ? e.message : String(e);
1527
+ console.error(`[recorder] Checkpoint overlay injection failed (non-fatal): ${msg}`);
1506
1528
  }
1507
1529
  try {
1508
1530
  await page.evaluate(`
@@ -1654,7 +1676,11 @@ var SessionRecorder = class _SessionRecorder {
1654
1676
  await this.injectActionScript(page);
1655
1677
  return;
1656
1678
  }
1657
- } catch {
1679
+ } catch (e) {
1680
+ const msg = e instanceof Error ? e.message : String(e);
1681
+ if (attempt < 4) {
1682
+ console.error(`[recorder] injectActionScript retry ${attempt + 1}/5 failed: ${msg}`);
1683
+ }
1658
1684
  }
1659
1685
  await new Promise((r) => setTimeout(r, 1e3));
1660
1686
  }
@@ -1674,7 +1700,9 @@ var SessionRecorder = class _SessionRecorder {
1674
1700
  if (url && url !== "about:blank" && !url.startsWith("chrome")) {
1675
1701
  try {
1676
1702
  await this.injectActionScript(page);
1677
- } catch {
1703
+ } catch (e) {
1704
+ const msg = e instanceof Error ? e.message : String(e);
1705
+ console.error(`[recorder] injectActionScript after navigation failed: ${msg}`);
1678
1706
  }
1679
1707
  }
1680
1708
  });
@@ -4965,7 +4965,7 @@ async function closeSessionByName(name) {
4965
4965
  } catch {
4966
4966
  }
4967
4967
  try {
4968
- const { SessionRecorder } = await import("./session-recorder-H2WPBCSW.js");
4968
+ const { SessionRecorder } = await import("./session-recorder-33UKWCIR.js");
4969
4969
  SessionRecorder.cleanup(session.name);
4970
4970
  } catch {
4971
4971
  }
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  SessionRecorder
4
- } from "./chunk-XU5WOJIV.js";
4
+ } from "./chunk-RMYEHTLS.js";
5
5
  import {
6
6
  addKnownIssue,
7
7
  getKnowledgePath,
@@ -25,7 +25,7 @@ import {
25
25
  resolveLaunchOpts,
26
26
  saveSessionDiskMeta,
27
27
  setActivePage
28
- } from "./chunk-U25ZKXBJ.js";
28
+ } from "./chunk-SJLRCE6N.js";
29
29
  import "./chunk-TNEN6VQ2.js";
30
30
  import {
31
31
  forwardCommandLog,
@@ -7205,9 +7205,14 @@ async function executeCommand(commandName, params, sessionName = "default", extr
7205
7205
  const { _target: _u, ...rest } = params;
7206
7206
  params = rest;
7207
7207
  }
7208
+ const _tabIndex = params._tabIndex;
7209
+ if (_tabIndex !== void 0) {
7210
+ const { _tabIndex: _u, ...rest } = params;
7211
+ params = rest;
7212
+ }
7208
7213
  let targetPageOverride = null;
7209
7214
  if (_target && extraOpts?.cdpEndpoint) {
7210
- const { findTargetPage } = await import("./browser-PUAJ3AHG.js");
7215
+ const { findTargetPage } = await import("./browser-XPKM344Y.js");
7211
7216
  targetPageOverride = await findTargetPage(extraOpts.cdpEndpoint, _target);
7212
7217
  if (!targetPageOverride) {
7213
7218
  return errorResult(`Target "${_target}" not found. Use 'xbrowser targets --cdp ${extraOpts.cdpEndpoint}' to list available pages.`);
@@ -7279,6 +7284,16 @@ async function executeCommand(commandName, params, sessionName = "default", extr
7279
7284
  cliName: "xbrowser",
7280
7285
  tips: new TipCollector2()
7281
7286
  };
7287
+ if (_tabIndex !== void 0 && session?.context) {
7288
+ const pages = session.context.pages();
7289
+ if (_tabIndex >= 0 && _tabIndex < pages.length) {
7290
+ const targetPage = pages[_tabIndex];
7291
+ await targetPage.bringToFront().catch(() => {
7292
+ });
7293
+ setActivePage(session, targetPage);
7294
+ ctx.page = targetPage;
7295
+ }
7296
+ }
7282
7297
  const start = Date.now();
7283
7298
  if (session) {
7284
7299
  streamCommandEvent(session.id, {
@@ -7307,7 +7322,17 @@ async function executeCommand(commandName, params, sessionName = "default", extr
7307
7322
  if (hooks.length > 0 && session?.page) {
7308
7323
  await Promise.all(hooks.map((h) => h.onBeforeCommand?.({ page: session.page, command: commandName, params })));
7309
7324
  }
7310
- const raw = await command.handler(params, ctx);
7325
+ let raw;
7326
+ const handlerPromise = command.handler(params, ctx);
7327
+ if (extraOpts?.timeout && extraOpts.timeout > 0) {
7328
+ const timeoutMs = extraOpts.timeout;
7329
+ const timeoutPromise = new Promise(
7330
+ (_, reject) => setTimeout(() => reject(new Error(`Command timed out after ${timeoutMs}ms`)), timeoutMs)
7331
+ );
7332
+ raw = await Promise.race([handlerPromise, timeoutPromise]);
7333
+ } else {
7334
+ raw = await handlerPromise;
7335
+ }
7311
7336
  const end = Date.now();
7312
7337
  const duration = end - start;
7313
7338
  let hookOutputs;
@@ -7883,16 +7908,29 @@ var configBuiltin = {
7883
7908
  execute: async (args, _options, _ctx) => {
7884
7909
  const [subcommand, ...rest] = args;
7885
7910
  if (!subcommand || subcommand === "list") {
7911
+ let flatten2 = function(obj, prefix = "") {
7912
+ const entries2 = [];
7913
+ for (const [k, v] of Object.entries(obj)) {
7914
+ const fullKey = prefix ? `${prefix}.${k}` : k;
7915
+ if (v && typeof v === "object" && !Array.isArray(v)) {
7916
+ entries2.push(...flatten2(v, fullKey));
7917
+ } else {
7918
+ entries2.push({ key: fullKey, value: v });
7919
+ }
7920
+ }
7921
+ return entries2;
7922
+ };
7923
+ var flatten = flatten2;
7886
7924
  const config = loadConfig();
7887
- const keys = Object.keys(config);
7888
- if (keys.length === 0) {
7925
+ const entries = flatten2(config);
7926
+ if (entries.length === 0) {
7889
7927
  console.log("Configuration is empty");
7890
7928
  return;
7891
7929
  }
7892
7930
  console.log("Configuration:");
7893
7931
  console.log("");
7894
- for (const k of keys) {
7895
- console.log(` ${k} = ${config[k]}`);
7932
+ for (const { key, value } of entries) {
7933
+ console.log(` ${key} = ${value}`);
7896
7934
  }
7897
7935
  return;
7898
7936
  }
@@ -8691,6 +8729,39 @@ function outputError(message) {
8691
8729
  console.error(formatted);
8692
8730
  process.exit(1);
8693
8731
  }
8732
+ function outputEnvelope(result, meta, mode) {
8733
+ if (mode !== "json" && mode !== "yaml") {
8734
+ if (!result.success) {
8735
+ outputError(result.message || "Unknown error");
8736
+ return;
8737
+ }
8738
+ outputResult(result.data, mode);
8739
+ return;
8740
+ }
8741
+ const { command, ...extraMeta } = meta;
8742
+ const commandResult = {
8743
+ success: result.success,
8744
+ data: result.data,
8745
+ message: result.message,
8746
+ tips: [],
8747
+ meta: {
8748
+ duration: result.duration ?? 0,
8749
+ ...extraMeta
8750
+ }
8751
+ };
8752
+ const formatted = outputFormatter.formatEnvelope(commandResult, {
8753
+ command,
8754
+ extraMeta,
8755
+ mode
8756
+ });
8757
+ console.log(formatted);
8758
+ if (result.tips?.length) {
8759
+ for (const tip of result.tips) {
8760
+ const text = typeof tip === "string" ? tip : tip.message;
8761
+ if (text) console.error(` \u{1F4A1} ${text}`);
8762
+ }
8763
+ }
8764
+ }
8694
8765
 
8695
8766
  // src/builtins/plugin.ts
8696
8767
  var pluginLoader2 = null;
@@ -10250,9 +10321,13 @@ async function handleBrowserCommand(command, args, options, sessionName, mode, c
10250
10321
  if (target) {
10251
10322
  params = { ...params, _target: target };
10252
10323
  }
10324
+ const tabIndex = options.tab;
10325
+ if (tabIndex !== void 0) {
10326
+ params = { ...params, _tabIndex: Number(tabIndex) };
10327
+ }
10253
10328
  const result = cdpEndpoint ? await executeCommand(cmdName, params, sessionName, { cdpEndpoint }) : await executeCommand(cmdName, params, sessionName);
10254
10329
  if (mode === "json" || mode === "yaml") {
10255
- outputResult(result, mode);
10330
+ outputEnvelope(result, { command: cmdName }, mode);
10256
10331
  } else if (!result.success) {
10257
10332
  outputError(result.message || "Command failed");
10258
10333
  } else {
@@ -10312,7 +10387,7 @@ async function handleSession(args, options, mode, _cdpEndpoint) {
10312
10387
  }
10313
10388
  } catch {
10314
10389
  }
10315
- outputResult({ ok: true, closed: count, all: true }, mode);
10390
+ outputEnvelope({ success: true, data: { closed: count, all: true } }, { command: "session close" }, mode);
10316
10391
  } else {
10317
10392
  const name = options.session || options.name || process.env.XBROWSER_SESSION || "default";
10318
10393
  try {
@@ -10320,7 +10395,7 @@ async function handleSession(args, options, mode, _cdpEndpoint) {
10320
10395
  } catch {
10321
10396
  }
10322
10397
  await closeSession(name);
10323
- outputResult({ ok: true, name }, mode);
10398
+ outputEnvelope({ success: true, data: { name } }, { command: "session close" }, mode);
10324
10399
  }
10325
10400
  break;
10326
10401
  }
@@ -10328,10 +10403,10 @@ async function handleSession(args, options, mode, _cdpEndpoint) {
10328
10403
  case "ls": {
10329
10404
  try {
10330
10405
  const sessions2 = await forwardSessionList();
10331
- outputResult({ sessions: sessions2 }, mode);
10406
+ outputEnvelope({ success: true, data: { sessions: sessions2 } }, { command: "session list" }, mode);
10332
10407
  } catch {
10333
10408
  const sessions2 = await listSessions();
10334
- outputResult({ sessions: sessions2 }, mode);
10409
+ outputEnvelope({ success: true, data: { sessions: sessions2 } }, { command: "session list" }, mode);
10335
10410
  }
10336
10411
  break;
10337
10412
  }
@@ -10346,7 +10421,7 @@ async function handleSession(args, options, mode, _cdpEndpoint) {
10346
10421
  await stopDaemonProcess();
10347
10422
  } catch {
10348
10423
  }
10349
- outputResult({ ok: true, name, killed: true, daemon: "stopped" }, mode);
10424
+ outputEnvelope({ success: true, data: { name, killed: true, daemon: "stopped" } }, { command: "session kill" }, mode);
10350
10425
  break;
10351
10426
  }
10352
10427
  case "kill-all": {
@@ -10365,7 +10440,7 @@ async function handleSession(args, options, mode, _cdpEndpoint) {
10365
10440
  } catch {
10366
10441
  }
10367
10442
  const cleaned = cleanSessionFiles();
10368
- outputResult({ ok: true, sessionsCleaned: cleaned, daemon: "killed" }, mode);
10443
+ outputEnvelope({ success: true, data: { sessionsCleaned: cleaned, daemon: "killed" } }, { command: "session kill-all" }, mode);
10369
10444
  break;
10370
10445
  }
10371
10446
  default:
@@ -10485,7 +10560,7 @@ async function handleSearch(args, options, mode) {
10485
10560
  }
10486
10561
  }
10487
10562
  if (mode === "json") {
10488
- outputResult({ results, total: results.length }, mode);
10563
+ outputEnvelope({ success: true, data: { results, total: results.length } }, { command: "plugin search" }, mode);
10489
10564
  } else {
10490
10565
  if (results.length === 0) {
10491
10566
  console.log("No plugins found");
@@ -10514,7 +10589,7 @@ async function handlePluginInfo(args, options, mode) {
10514
10589
  if (pluginInfo) {
10515
10590
  const d = pluginInfo;
10516
10591
  if (mode === "json") {
10517
- outputResult({ source: "marketplace", ...d }, mode);
10592
+ outputEnvelope({ success: true, data: { source: "marketplace", ...d } }, { command: "plugin info" }, mode);
10518
10593
  return;
10519
10594
  }
10520
10595
  console.log(`\u540D\u79F0: ${d.name || ""}`);
@@ -10540,7 +10615,7 @@ async function handlePluginInfo(args, options, mode) {
10540
10615
  const pkg = latest && versions?.[latest];
10541
10616
  if (pkg) {
10542
10617
  if (mode === "json") {
10543
- outputResult({ source: "npm", name: pkg.name, version: latest, description: pkg.description }, mode);
10618
+ outputEnvelope({ success: true, data: { source: "npm", name: pkg.name, version: latest, description: pkg.description } }, { command: "plugin info" }, mode);
10544
10619
  return;
10545
10620
  }
10546
10621
  console.log(`\u540D\u79F0: ${pkg.name || ""}`);
@@ -10569,7 +10644,7 @@ async function handlePluginSchema(args, mode) {
10569
10644
  return;
10570
10645
  }
10571
10646
  if (mode === "json") {
10572
- outputResult(contract, mode);
10647
+ outputEnvelope({ success: true, data: contract }, { command: "plugin schema" }, mode);
10573
10648
  return;
10574
10649
  }
10575
10650
  if ("commands" in contract) {
@@ -10647,8 +10722,9 @@ async function handlePlugin(args, options, mode) {
10647
10722
  }
10648
10723
  } catch {
10649
10724
  }
10650
- outputResult(
10651
- { ok: true, name: result.name, source: result.source, path: result.path },
10725
+ outputEnvelope(
10726
+ { success: true, data: { name: result.name, source: result.source, path: result.path } },
10727
+ { command: "plugin install" },
10652
10728
  mode
10653
10729
  );
10654
10730
  break;
@@ -10667,7 +10743,7 @@ async function handlePlugin(args, options, mode) {
10667
10743
  await loader.reloadPlugin(name);
10668
10744
  } catch {
10669
10745
  }
10670
- outputResult({ ok: true, name }, mode);
10746
+ outputEnvelope({ success: true, data: { name } }, { command: "plugin uninstall" }, mode);
10671
10747
  break;
10672
10748
  }
10673
10749
  case "list": {
@@ -10689,7 +10765,7 @@ async function handlePlugin(args, options, mode) {
10689
10765
  };
10690
10766
  });
10691
10767
  if (mode === "json") {
10692
- outputResult({ plugins: enrichedPlugins }, mode);
10768
+ outputEnvelope({ success: true, data: { plugins: enrichedPlugins } }, { command: "plugin list" }, mode);
10693
10769
  } else {
10694
10770
  if (enrichedPlugins.length === 0) {
10695
10771
  console.log("No plugins installed");
@@ -10725,7 +10801,7 @@ Total: ${enrichedPlugins.length} plugins`);
10725
10801
  } catch {
10726
10802
  outputError(`Plugin "${name}" not found. Use 'xbrowser plugin list' to see installed plugins.`);
10727
10803
  }
10728
- outputResult({ ok: true, name }, mode);
10804
+ outputEnvelope({ success: true, data: { name } }, { command: "plugin reload" }, mode);
10729
10805
  break;
10730
10806
  }
10731
10807
  case "search":
@@ -10757,24 +10833,22 @@ function handleDaemon(args, options, mode) {
10757
10833
  case "start": {
10758
10834
  const port = options.port ? Number(options.port) : 9224;
10759
10835
  startDaemonProcess(port).then(
10760
- (config) => outputResult({ ok: true, pid: config.pid, port: config.port }, mode)
10836
+ (config) => outputEnvelope({ success: true, data: { pid: config.pid, port: config.port } }, { command: "daemon start" }, mode)
10761
10837
  ).catch(
10762
10838
  (e) => outputError(e instanceof Error ? e.message : String(e))
10763
10839
  );
10764
10840
  break;
10765
10841
  }
10766
10842
  case "stop": {
10767
- stopDaemonProcess().then(() => outputResult({ ok: true }, mode)).catch(
10843
+ stopDaemonProcess().then(() => outputEnvelope({ success: true, data: {} }, { command: "daemon stop" }, mode)).catch(
10768
10844
  (e) => outputError(e instanceof Error ? e.message : String(e))
10769
10845
  );
10770
10846
  break;
10771
10847
  }
10772
10848
  case "status": {
10773
10849
  const status = getDaemonProcessStatus();
10774
- outputResult(
10775
- status.running ? { running: true, pid: status.pid, port: status.port } : { running: false },
10776
- mode
10777
- );
10850
+ const statusData = status.running ? { running: true, pid: status.pid, port: status.port } : { running: false };
10851
+ outputEnvelope({ success: true, data: statusData }, { command: "daemon status" }, mode);
10778
10852
  break;
10779
10853
  }
10780
10854
  default:
@@ -11152,7 +11226,7 @@ async function handleFilter(args, _mode, options) {
11152
11226
  console.log(` Original: ${result.originalCount}, After: ${result.filteredCount}, Removed: ${result.removed} (${result.percentage}%)`);
11153
11227
  }
11154
11228
  async function handleGeneratePlugin(sessionName, pluginName, outputDir) {
11155
- const { SessionRecorder: SessionRecorder2 } = await import("./session-recorder-H2WPBCSW.js");
11229
+ const { SessionRecorder: SessionRecorder2 } = await import("./session-recorder-33UKWCIR.js");
11156
11230
  const { readSiteKnowledge: readSiteKnowledge2, toMarkdown } = await import("./site-knowledge-SYC6VCDB.js");
11157
11231
  const { mkdirSync: mkdirSync10, writeFileSync: writeFileSync12 } = await import("fs");
11158
11232
  const { join: join14 } = await import("path");
@@ -12499,7 +12573,8 @@ var KNOWN_GLOBAL_OPTIONS = /* @__PURE__ */ new Set([
12499
12573
  "port",
12500
12574
  "token",
12501
12575
  "timeout",
12502
- "headless"
12576
+ "headless",
12577
+ "tab"
12503
12578
  ]);
12504
12579
  function showCommandHelp(siteName, cmd, siteConfig, mode) {
12505
12580
  const c = cmd;
@@ -12653,20 +12728,28 @@ async function handleEvalMode(argv) {
12653
12728
  }
12654
12729
  async function handleChainInput(input, argv) {
12655
12730
  const cdpEndpoint = argv ? extractCdpFromArgv(argv) : void 0;
12656
- const jsonMode = argv ? argv.some((a) => a === "--json" || a.startsWith("--json=") || a.includes(" --json") || a.startsWith("--json")) || argv.includes("-j") : false;
12731
+ const hasJson = argv ? argv.some((a) => a === "--json" || a.startsWith("--json=") || a.includes(" --json") || a.startsWith("--json")) || argv.includes("-j") : false;
12732
+ const hasYaml = argv ? argv.some((a) => a === "--yaml" || a.startsWith("--yaml=")) : false;
12733
+ const mode = hasJson ? "json" : hasYaml ? "yaml" : "text";
12657
12734
  const chainResult = await executeChain(input, { cdpEndpoint });
12658
- if (jsonMode) {
12659
- const output = {
12660
- success: chainResult.success,
12661
- steps: chainResult.steps.map((s) => ({
12662
- command: s.raw,
12663
- success: s.success,
12664
- data: s.data,
12665
- duration: s.duration,
12666
- ...s.hookOutputs?.length ? { hooks: s.hookOutputs } : {}
12667
- }))
12668
- };
12669
- console.log(JSON.stringify(output, null, 2));
12735
+ if (mode === "json" || mode === "yaml") {
12736
+ outputEnvelope(
12737
+ {
12738
+ success: chainResult.success,
12739
+ data: {
12740
+ steps: chainResult.steps.map((s) => ({
12741
+ command: s.raw,
12742
+ success: s.success,
12743
+ data: s.data,
12744
+ duration: s.duration,
12745
+ ...s.hookOutputs?.length ? { hooks: s.hookOutputs } : {}
12746
+ }))
12747
+ },
12748
+ duration: chainResult.totalDuration
12749
+ },
12750
+ { command: "chain", totalSteps: chainResult.steps.length },
12751
+ mode
12752
+ );
12670
12753
  } else {
12671
12754
  printChainResult(chainResult);
12672
12755
  }
@@ -12733,7 +12816,15 @@ async function routeCommand(argvIn, stdinCommands) {
12733
12816
  const sessionName = options.session || process.env.XBROWSER_SESSION || "default";
12734
12817
  const cdpEndpoint = options.cdp || process.env.XBROWSER_CDP;
12735
12818
  if (options.version || options.v && positional.length === 0) {
12736
- console.log(`xbrowser v${version}`);
12819
+ if (mode === "json") {
12820
+ outputEnvelope(
12821
+ { success: true, data: { version, name: "@xbrowser/cli" } },
12822
+ { command: "version" },
12823
+ mode
12824
+ );
12825
+ } else {
12826
+ console.log(`xbrowser v${version}`);
12827
+ }
12737
12828
  return;
12738
12829
  }
12739
12830
  if (positional.length === 0) {
@@ -12929,18 +13020,23 @@ async function routeCommand(argvIn, stdinCommands) {
12929
13020
  if (isChainInput(fullInput)) {
12930
13021
  const chainResult = await executeChain(fullInput, { cdpEndpoint, sessionName });
12931
13022
  if (mode === "json" || mode === "yaml") {
12932
- const output = {
12933
- success: chainResult.success,
12934
- steps: chainResult.steps.map((s) => ({
12935
- command: s.raw,
12936
- success: s.success,
12937
- data: s.data,
12938
- duration: s.duration
12939
- })),
12940
- totalDuration: chainResult.totalDuration,
12941
- ...chainResult.stoppedReason ? { stoppedReason: chainResult.stoppedReason } : {}
12942
- };
12943
- outputResult(output, mode);
13023
+ outputEnvelope(
13024
+ {
13025
+ success: chainResult.success,
13026
+ data: {
13027
+ steps: chainResult.steps.map((s) => ({
13028
+ command: s.raw,
13029
+ success: s.success,
13030
+ data: s.data,
13031
+ duration: s.duration
13032
+ })),
13033
+ ...chainResult.stoppedReason ? { stoppedReason: chainResult.stoppedReason } : {}
13034
+ },
13035
+ duration: chainResult.totalDuration
13036
+ },
13037
+ { command: "chain", totalSteps: chainResult.steps.length },
13038
+ mode
13039
+ );
12944
13040
  if (!chainResult.success) throw new Error("Command failed");
12945
13041
  return;
12946
13042
  }
@@ -13085,6 +13181,17 @@ Run "xbrowser ${command} ${subCommand} --help" to see available parameters.`
13085
13181
  session = await createSession(sessionName, void 0, cdpEndpoint ? { cdpEndpoint } : {});
13086
13182
  }
13087
13183
  }
13184
+ const cmdTabIndex = options.tab !== void 0 ? Number(options.tab) : void 0;
13185
+ if (cmdTabIndex !== void 0 && session?.context) {
13186
+ const pages = session.context.pages();
13187
+ if (cmdTabIndex >= 0 && cmdTabIndex < pages.length) {
13188
+ const targetPage = pages[cmdTabIndex];
13189
+ await targetPage.bringToFront().catch(() => {
13190
+ });
13191
+ const { setActivePage: setActivePage2 } = await import("./browser-XPKM344Y.js");
13192
+ setActivePage2(session, targetPage);
13193
+ }
13194
+ }
13088
13195
  const ctx = {
13089
13196
  args: cmdArgsForPlugin,
13090
13197
  options,
@@ -13162,22 +13269,17 @@ Run "xbrowser ${command} ${subCommand} --help" to see available parameters.`
13162
13269
  const outputData = isCommandResult2(result) ? result.data : result && typeof result === "object" ? result.data ?? result : result;
13163
13270
  const tips = isCommandResult2(result) ? result.tips : result && typeof result === "object" ? result.tips : void 0;
13164
13271
  if (mode === "json" || mode === "yaml") {
13165
- const finalOutput = {
13166
- data: outputData
13167
- };
13168
- if (injectedViewerUrl) {
13169
- finalOutput.viewerUrl = injectedViewerUrl;
13170
- }
13171
- if (tips?.length) {
13172
- finalOutput.tips = tips;
13173
- }
13174
- if (hookOutputs.length > 0) {
13175
- finalOutput.hooks = hookOutputs;
13176
- }
13177
- console.log(outputFormatter2.format(finalOutput, { mode, color: false, emoji: false }));
13178
- if (tips?.length) {
13179
- for (const tip of tips) console.error(`\u{1F4A1} ${typeof tip === "string" ? tip : tip.message}`);
13180
- }
13272
+ const resultSuccess = isCommandResult2(result) ? result.success !== false : true;
13273
+ const resultMsg = isCommandResult2(result) ? result.message : void 0;
13274
+ const duration = Date.now() - cmdStart;
13275
+ const envelopeMeta = { command: `${command} ${subCommand}` };
13276
+ if (injectedViewerUrl) envelopeMeta.viewerUrl = injectedViewerUrl;
13277
+ if (hookOutputs.length > 0) envelopeMeta.hooks = hookOutputs;
13278
+ outputEnvelope(
13279
+ { success: resultSuccess, data: outputData, message: resultMsg, tips, duration },
13280
+ envelopeMeta,
13281
+ mode
13282
+ );
13181
13283
  } else {
13182
13284
  console.log(outputFormatter2.format(outputData, { mode: "text", color: true, emoji: true }));
13183
13285
  if (tips?.length) {
@@ -13359,7 +13461,7 @@ async function main() {
13359
13461
  const command = process.argv[2];
13360
13462
  const isLongRunning = command === "preview" || command === "serve";
13361
13463
  if (!isLongRunning) {
13362
- const { ensureProcessCanExit } = await import("./browser-PUAJ3AHG.js");
13464
+ const { ensureProcessCanExit } = await import("./browser-XPKM344Y.js");
13363
13465
  await ensureProcessCanExit().catch(() => {
13364
13466
  });
13365
13467
  process.exit(exitCode);
@@ -5,7 +5,7 @@ import {
5
5
  } from "./chunk-VEDJ5XSQ.js";
6
6
  import {
7
7
  SessionRecorder
8
- } from "./chunk-BALOM2HW.js";
8
+ } from "./chunk-FL6DOSWV.js";
9
9
  import {
10
10
  closeEphemeralContext,
11
11
  closeSessionByName,
@@ -21,7 +21,7 @@ import {
21
21
  resolveLaunchOpts,
22
22
  saveSessionDiskMeta,
23
23
  setActivePage
24
- } from "./chunk-TAHJCCME.js";
24
+ } from "./chunk-HXLMMSU3.js";
25
25
  import "./chunk-VJNMAWPZ.js";
26
26
  import "./chunk-TNEN6VQ2.js";
27
27
  import {
@@ -6742,9 +6742,14 @@ async function executeCommand(commandName, params, sessionName = "default", extr
6742
6742
  const { _target: _u, ...rest } = params;
6743
6743
  params = rest;
6744
6744
  }
6745
+ const _tabIndex = params._tabIndex;
6746
+ if (_tabIndex !== void 0) {
6747
+ const { _tabIndex: _u, ...rest } = params;
6748
+ params = rest;
6749
+ }
6745
6750
  let targetPageOverride = null;
6746
6751
  if (_target && extraOpts?.cdpEndpoint) {
6747
- const { findTargetPage } = await import("./browser-NSSCH4MJ.js");
6752
+ const { findTargetPage } = await import("./browser-LW2MDJE4.js");
6748
6753
  targetPageOverride = await findTargetPage(extraOpts.cdpEndpoint, _target);
6749
6754
  if (!targetPageOverride) {
6750
6755
  return errorResult(`Target "${_target}" not found. Use 'xbrowser targets --cdp ${extraOpts.cdpEndpoint}' to list available pages.`);
@@ -6816,6 +6821,16 @@ async function executeCommand(commandName, params, sessionName = "default", extr
6816
6821
  cliName: "xbrowser",
6817
6822
  tips: new TipCollector2()
6818
6823
  };
6824
+ if (_tabIndex !== void 0 && session?.context) {
6825
+ const pages = session.context.pages();
6826
+ if (_tabIndex >= 0 && _tabIndex < pages.length) {
6827
+ const targetPage = pages[_tabIndex];
6828
+ await targetPage.bringToFront().catch(() => {
6829
+ });
6830
+ setActivePage(session, targetPage);
6831
+ ctx.page = targetPage;
6832
+ }
6833
+ }
6819
6834
  const start = Date.now();
6820
6835
  if (session) {
6821
6836
  streamCommandEvent(session.id, {
@@ -6844,7 +6859,17 @@ async function executeCommand(commandName, params, sessionName = "default", extr
6844
6859
  if (hooks.length > 0 && session?.page) {
6845
6860
  await Promise.all(hooks.map((h) => h.onBeforeCommand?.({ page: session.page, command: commandName, params })));
6846
6861
  }
6847
- const raw = await command.handler(params, ctx);
6862
+ let raw;
6863
+ const handlerPromise = command.handler(params, ctx);
6864
+ if (extraOpts?.timeout && extraOpts.timeout > 0) {
6865
+ const timeoutMs = extraOpts.timeout;
6866
+ const timeoutPromise = new Promise(
6867
+ (_, reject) => setTimeout(() => reject(new Error(`Command timed out after ${timeoutMs}ms`)), timeoutMs)
6868
+ );
6869
+ raw = await Promise.race([handlerPromise, timeoutPromise]);
6870
+ } else {
6871
+ raw = await handlerPromise;
6872
+ }
6848
6873
  const end = Date.now();
6849
6874
  const duration = end - start;
6850
6875
  let hookOutputs;
package/dist/index.d.ts CHANGED
@@ -737,6 +737,7 @@ declare function setWSServer(server: WSServer | null): void;
737
737
  declare function executeCommand(commandName: string, params: Record<string, unknown>, sessionName?: string, extraOpts?: {
738
738
  cdpEndpoint?: string;
739
739
  skipCleanup?: boolean;
740
+ timeout?: number;
740
741
  }): Promise<ExecutionResult>;
741
742
  /**
742
743
  * Execute a chain of browser commands parsed from a string expression.
package/dist/index.js CHANGED
@@ -57,7 +57,7 @@ import {
57
57
  } from "./chunk-GJAV3QGG.js";
58
58
  import {
59
59
  SessionRecorder
60
- } from "./chunk-XU5WOJIV.js";
60
+ } from "./chunk-RMYEHTLS.js";
61
61
  import {
62
62
  addKnownIssue,
63
63
  getKnowledgePath,
@@ -82,7 +82,7 @@ import {
82
82
  resolveLaunchOpts,
83
83
  saveSessionDiskMeta,
84
84
  setActivePage
85
- } from "./chunk-7EOQV6ZW.js";
85
+ } from "./chunk-CJJ564VK.js";
86
86
  import "./chunk-VJNMAWPZ.js";
87
87
  import "./chunk-TNEN6VQ2.js";
88
88
  import {
@@ -7525,9 +7525,14 @@ async function executeCommand(commandName, params, sessionName = "default", extr
7525
7525
  const { _target: _u, ...rest } = params;
7526
7526
  params = rest;
7527
7527
  }
7528
+ const _tabIndex = params._tabIndex;
7529
+ if (_tabIndex !== void 0) {
7530
+ const { _tabIndex: _u, ...rest } = params;
7531
+ params = rest;
7532
+ }
7528
7533
  let targetPageOverride = null;
7529
7534
  if (_target && extraOpts?.cdpEndpoint) {
7530
- const { findTargetPage } = await import("./browser-PTELW3WS.js");
7535
+ const { findTargetPage } = await import("./browser-TLIDFFEG.js");
7531
7536
  targetPageOverride = await findTargetPage(extraOpts.cdpEndpoint, _target);
7532
7537
  if (!targetPageOverride) {
7533
7538
  return errorResult(`Target "${_target}" not found. Use 'xbrowser targets --cdp ${extraOpts.cdpEndpoint}' to list available pages.`);
@@ -7599,6 +7604,16 @@ async function executeCommand(commandName, params, sessionName = "default", extr
7599
7604
  cliName: "xbrowser",
7600
7605
  tips: new TipCollector2()
7601
7606
  };
7607
+ if (_tabIndex !== void 0 && session?.context) {
7608
+ const pages = session.context.pages();
7609
+ if (_tabIndex >= 0 && _tabIndex < pages.length) {
7610
+ const targetPage = pages[_tabIndex];
7611
+ await targetPage.bringToFront().catch(() => {
7612
+ });
7613
+ setActivePage(session, targetPage);
7614
+ ctx.page = targetPage;
7615
+ }
7616
+ }
7602
7617
  const start = Date.now();
7603
7618
  if (session) {
7604
7619
  streamCommandEvent(session.id, {
@@ -7627,7 +7642,17 @@ async function executeCommand(commandName, params, sessionName = "default", extr
7627
7642
  if (hooks.length > 0 && session?.page) {
7628
7643
  await Promise.all(hooks.map((h) => h.onBeforeCommand?.({ page: session.page, command: commandName, params })));
7629
7644
  }
7630
- const raw = await command.handler(params, ctx);
7645
+ let raw;
7646
+ const handlerPromise = command.handler(params, ctx);
7647
+ if (extraOpts?.timeout && extraOpts.timeout > 0) {
7648
+ const timeoutMs = extraOpts.timeout;
7649
+ const timeoutPromise = new Promise(
7650
+ (_, reject) => setTimeout(() => reject(new Error(`Command timed out after ${timeoutMs}ms`)), timeoutMs)
7651
+ );
7652
+ raw = await Promise.race([handlerPromise, timeoutPromise]);
7653
+ } else {
7654
+ raw = await handlerPromise;
7655
+ }
7631
7656
  const end = Date.now();
7632
7657
  const duration = end - start;
7633
7658
  let hookOutputs;
@@ -8201,16 +8226,29 @@ var configBuiltin = {
8201
8226
  execute: async (args, _options, _ctx) => {
8202
8227
  const [subcommand, ...rest] = args;
8203
8228
  if (!subcommand || subcommand === "list") {
8229
+ let flatten2 = function(obj, prefix = "") {
8230
+ const entries2 = [];
8231
+ for (const [k, v] of Object.entries(obj)) {
8232
+ const fullKey = prefix ? `${prefix}.${k}` : k;
8233
+ if (v && typeof v === "object" && !Array.isArray(v)) {
8234
+ entries2.push(...flatten2(v, fullKey));
8235
+ } else {
8236
+ entries2.push({ key: fullKey, value: v });
8237
+ }
8238
+ }
8239
+ return entries2;
8240
+ };
8241
+ var flatten = flatten2;
8204
8242
  const config = loadConfig();
8205
- const keys = Object.keys(config);
8206
- if (keys.length === 0) {
8243
+ const entries = flatten2(config);
8244
+ if (entries.length === 0) {
8207
8245
  console.log("Configuration is empty");
8208
8246
  return;
8209
8247
  }
8210
8248
  console.log("Configuration:");
8211
8249
  console.log("");
8212
- for (const k of keys) {
8213
- console.log(` ${k} = ${config[k]}`);
8250
+ for (const { key, value } of entries) {
8251
+ console.log(` ${key} = ${value}`);
8214
8252
  }
8215
8253
  return;
8216
8254
  }
@@ -9009,6 +9047,39 @@ function outputError(message) {
9009
9047
  console.error(formatted);
9010
9048
  process.exit(1);
9011
9049
  }
9050
+ function outputEnvelope(result, meta, mode) {
9051
+ if (mode !== "json" && mode !== "yaml") {
9052
+ if (!result.success) {
9053
+ outputError(result.message || "Unknown error");
9054
+ return;
9055
+ }
9056
+ outputResult(result.data, mode);
9057
+ return;
9058
+ }
9059
+ const { command, ...extraMeta } = meta;
9060
+ const commandResult = {
9061
+ success: result.success,
9062
+ data: result.data,
9063
+ message: result.message,
9064
+ tips: [],
9065
+ meta: {
9066
+ duration: result.duration ?? 0,
9067
+ ...extraMeta
9068
+ }
9069
+ };
9070
+ const formatted = outputFormatter.formatEnvelope(commandResult, {
9071
+ command,
9072
+ extraMeta,
9073
+ mode
9074
+ });
9075
+ console.log(formatted);
9076
+ if (result.tips?.length) {
9077
+ for (const tip of result.tips) {
9078
+ const text = typeof tip === "string" ? tip : tip.message;
9079
+ if (text) console.error(` \u{1F4A1} ${text}`);
9080
+ }
9081
+ }
9082
+ }
9012
9083
 
9013
9084
  // src/builtins/plugin.ts
9014
9085
  var pluginLoader2 = null;
@@ -10573,9 +10644,13 @@ async function handleBrowserCommand(command, args, options, sessionName, mode, c
10573
10644
  if (target) {
10574
10645
  params = { ...params, _target: target };
10575
10646
  }
10647
+ const tabIndex = options.tab;
10648
+ if (tabIndex !== void 0) {
10649
+ params = { ...params, _tabIndex: Number(tabIndex) };
10650
+ }
10576
10651
  const result = cdpEndpoint ? await executeCommand(cmdName, params, sessionName, { cdpEndpoint }) : await executeCommand(cmdName, params, sessionName);
10577
10652
  if (mode === "json" || mode === "yaml") {
10578
- outputResult(result, mode);
10653
+ outputEnvelope(result, { command: cmdName }, mode);
10579
10654
  } else if (!result.success) {
10580
10655
  outputError(result.message || "Command failed");
10581
10656
  } else {
@@ -10635,7 +10710,7 @@ async function handleSession(args, options, mode, _cdpEndpoint) {
10635
10710
  }
10636
10711
  } catch {
10637
10712
  }
10638
- outputResult({ ok: true, closed: count, all: true }, mode);
10713
+ outputEnvelope({ success: true, data: { closed: count, all: true } }, { command: "session close" }, mode);
10639
10714
  } else {
10640
10715
  const name = options.session || options.name || process.env.XBROWSER_SESSION || "default";
10641
10716
  try {
@@ -10643,7 +10718,7 @@ async function handleSession(args, options, mode, _cdpEndpoint) {
10643
10718
  } catch {
10644
10719
  }
10645
10720
  await closeSession(name);
10646
- outputResult({ ok: true, name }, mode);
10721
+ outputEnvelope({ success: true, data: { name } }, { command: "session close" }, mode);
10647
10722
  }
10648
10723
  break;
10649
10724
  }
@@ -10651,10 +10726,10 @@ async function handleSession(args, options, mode, _cdpEndpoint) {
10651
10726
  case "ls": {
10652
10727
  try {
10653
10728
  const sessions2 = await forwardSessionList();
10654
- outputResult({ sessions: sessions2 }, mode);
10729
+ outputEnvelope({ success: true, data: { sessions: sessions2 } }, { command: "session list" }, mode);
10655
10730
  } catch {
10656
10731
  const sessions2 = await listSessions();
10657
- outputResult({ sessions: sessions2 }, mode);
10732
+ outputEnvelope({ success: true, data: { sessions: sessions2 } }, { command: "session list" }, mode);
10658
10733
  }
10659
10734
  break;
10660
10735
  }
@@ -10669,7 +10744,7 @@ async function handleSession(args, options, mode, _cdpEndpoint) {
10669
10744
  await stopDaemonProcess();
10670
10745
  } catch {
10671
10746
  }
10672
- outputResult({ ok: true, name, killed: true, daemon: "stopped" }, mode);
10747
+ outputEnvelope({ success: true, data: { name, killed: true, daemon: "stopped" } }, { command: "session kill" }, mode);
10673
10748
  break;
10674
10749
  }
10675
10750
  case "kill-all": {
@@ -10688,7 +10763,7 @@ async function handleSession(args, options, mode, _cdpEndpoint) {
10688
10763
  } catch {
10689
10764
  }
10690
10765
  const cleaned = cleanSessionFiles();
10691
- outputResult({ ok: true, sessionsCleaned: cleaned, daemon: "killed" }, mode);
10766
+ outputEnvelope({ success: true, data: { sessionsCleaned: cleaned, daemon: "killed" } }, { command: "session kill-all" }, mode);
10692
10767
  break;
10693
10768
  }
10694
10769
  default:
@@ -10808,7 +10883,7 @@ async function handleSearch(args, options, mode) {
10808
10883
  }
10809
10884
  }
10810
10885
  if (mode === "json") {
10811
- outputResult({ results, total: results.length }, mode);
10886
+ outputEnvelope({ success: true, data: { results, total: results.length } }, { command: "plugin search" }, mode);
10812
10887
  } else {
10813
10888
  if (results.length === 0) {
10814
10889
  console.log("No plugins found");
@@ -10837,7 +10912,7 @@ async function handlePluginInfo(args, options, mode) {
10837
10912
  if (pluginInfo) {
10838
10913
  const d = pluginInfo;
10839
10914
  if (mode === "json") {
10840
- outputResult({ source: "marketplace", ...d }, mode);
10915
+ outputEnvelope({ success: true, data: { source: "marketplace", ...d } }, { command: "plugin info" }, mode);
10841
10916
  return;
10842
10917
  }
10843
10918
  console.log(`\u540D\u79F0: ${d.name || ""}`);
@@ -10863,7 +10938,7 @@ async function handlePluginInfo(args, options, mode) {
10863
10938
  const pkg = latest && versions?.[latest];
10864
10939
  if (pkg) {
10865
10940
  if (mode === "json") {
10866
- outputResult({ source: "npm", name: pkg.name, version: latest, description: pkg.description }, mode);
10941
+ outputEnvelope({ success: true, data: { source: "npm", name: pkg.name, version: latest, description: pkg.description } }, { command: "plugin info" }, mode);
10867
10942
  return;
10868
10943
  }
10869
10944
  console.log(`\u540D\u79F0: ${pkg.name || ""}`);
@@ -10892,7 +10967,7 @@ async function handlePluginSchema(args, mode) {
10892
10967
  return;
10893
10968
  }
10894
10969
  if (mode === "json") {
10895
- outputResult(contract, mode);
10970
+ outputEnvelope({ success: true, data: contract }, { command: "plugin schema" }, mode);
10896
10971
  return;
10897
10972
  }
10898
10973
  if ("commands" in contract) {
@@ -10970,8 +11045,9 @@ async function handlePlugin(args, options, mode) {
10970
11045
  }
10971
11046
  } catch {
10972
11047
  }
10973
- outputResult(
10974
- { ok: true, name: result.name, source: result.source, path: result.path },
11048
+ outputEnvelope(
11049
+ { success: true, data: { name: result.name, source: result.source, path: result.path } },
11050
+ { command: "plugin install" },
10975
11051
  mode
10976
11052
  );
10977
11053
  break;
@@ -10990,7 +11066,7 @@ async function handlePlugin(args, options, mode) {
10990
11066
  await loader.reloadPlugin(name);
10991
11067
  } catch {
10992
11068
  }
10993
- outputResult({ ok: true, name }, mode);
11069
+ outputEnvelope({ success: true, data: { name } }, { command: "plugin uninstall" }, mode);
10994
11070
  break;
10995
11071
  }
10996
11072
  case "list": {
@@ -11012,7 +11088,7 @@ async function handlePlugin(args, options, mode) {
11012
11088
  };
11013
11089
  });
11014
11090
  if (mode === "json") {
11015
- outputResult({ plugins: enrichedPlugins }, mode);
11091
+ outputEnvelope({ success: true, data: { plugins: enrichedPlugins } }, { command: "plugin list" }, mode);
11016
11092
  } else {
11017
11093
  if (enrichedPlugins.length === 0) {
11018
11094
  console.log("No plugins installed");
@@ -11048,7 +11124,7 @@ Total: ${enrichedPlugins.length} plugins`);
11048
11124
  } catch {
11049
11125
  outputError(`Plugin "${name}" not found. Use 'xbrowser plugin list' to see installed plugins.`);
11050
11126
  }
11051
- outputResult({ ok: true, name }, mode);
11127
+ outputEnvelope({ success: true, data: { name } }, { command: "plugin reload" }, mode);
11052
11128
  break;
11053
11129
  }
11054
11130
  case "search":
@@ -11080,24 +11156,22 @@ function handleDaemon(args, options, mode) {
11080
11156
  case "start": {
11081
11157
  const port = options.port ? Number(options.port) : 9224;
11082
11158
  startDaemonProcess(port).then(
11083
- (config) => outputResult({ ok: true, pid: config.pid, port: config.port }, mode)
11159
+ (config) => outputEnvelope({ success: true, data: { pid: config.pid, port: config.port } }, { command: "daemon start" }, mode)
11084
11160
  ).catch(
11085
11161
  (e) => outputError(e instanceof Error ? e.message : String(e))
11086
11162
  );
11087
11163
  break;
11088
11164
  }
11089
11165
  case "stop": {
11090
- stopDaemonProcess().then(() => outputResult({ ok: true }, mode)).catch(
11166
+ stopDaemonProcess().then(() => outputEnvelope({ success: true, data: {} }, { command: "daemon stop" }, mode)).catch(
11091
11167
  (e) => outputError(e instanceof Error ? e.message : String(e))
11092
11168
  );
11093
11169
  break;
11094
11170
  }
11095
11171
  case "status": {
11096
11172
  const status = getDaemonProcessStatus();
11097
- outputResult(
11098
- status.running ? { running: true, pid: status.pid, port: status.port } : { running: false },
11099
- mode
11100
- );
11173
+ const statusData = status.running ? { running: true, pid: status.pid, port: status.port } : { running: false };
11174
+ outputEnvelope({ success: true, data: statusData }, { command: "daemon status" }, mode);
11101
11175
  break;
11102
11176
  }
11103
11177
  default:
@@ -11475,7 +11549,7 @@ async function handleFilter(args, _mode, options) {
11475
11549
  console.log(` Original: ${result.originalCount}, After: ${result.filteredCount}, Removed: ${result.removed} (${result.percentage}%)`);
11476
11550
  }
11477
11551
  async function handleGeneratePlugin(sessionName, pluginName, outputDir) {
11478
- const { SessionRecorder: SessionRecorder2 } = await import("./session-recorder-H2WPBCSW.js");
11552
+ const { SessionRecorder: SessionRecorder2 } = await import("./session-recorder-33UKWCIR.js");
11479
11553
  const { readSiteKnowledge: readSiteKnowledge2, toMarkdown } = await import("./site-knowledge-SYC6VCDB.js");
11480
11554
  const { mkdirSync: mkdirSync11, writeFileSync: writeFileSync13 } = await import("fs");
11481
11555
  const { join: join14 } = await import("path");
@@ -12822,7 +12896,8 @@ var KNOWN_GLOBAL_OPTIONS = /* @__PURE__ */ new Set([
12822
12896
  "port",
12823
12897
  "token",
12824
12898
  "timeout",
12825
- "headless"
12899
+ "headless",
12900
+ "tab"
12826
12901
  ]);
12827
12902
  function showCommandHelp(siteName, cmd, siteConfig, mode) {
12828
12903
  const c = cmd;
@@ -12976,20 +13051,28 @@ async function handleEvalMode(argv) {
12976
13051
  }
12977
13052
  async function handleChainInput(input, argv) {
12978
13053
  const cdpEndpoint = argv ? extractCdpFromArgv(argv) : void 0;
12979
- const jsonMode = argv ? argv.some((a) => a === "--json" || a.startsWith("--json=") || a.includes(" --json") || a.startsWith("--json")) || argv.includes("-j") : false;
13054
+ const hasJson = argv ? argv.some((a) => a === "--json" || a.startsWith("--json=") || a.includes(" --json") || a.startsWith("--json")) || argv.includes("-j") : false;
13055
+ const hasYaml = argv ? argv.some((a) => a === "--yaml" || a.startsWith("--yaml=")) : false;
13056
+ const mode = hasJson ? "json" : hasYaml ? "yaml" : "text";
12980
13057
  const chainResult = await executeChain(input, { cdpEndpoint });
12981
- if (jsonMode) {
12982
- const output = {
12983
- success: chainResult.success,
12984
- steps: chainResult.steps.map((s) => ({
12985
- command: s.raw,
12986
- success: s.success,
12987
- data: s.data,
12988
- duration: s.duration,
12989
- ...s.hookOutputs?.length ? { hooks: s.hookOutputs } : {}
12990
- }))
12991
- };
12992
- console.log(JSON.stringify(output, null, 2));
13058
+ if (mode === "json" || mode === "yaml") {
13059
+ outputEnvelope(
13060
+ {
13061
+ success: chainResult.success,
13062
+ data: {
13063
+ steps: chainResult.steps.map((s) => ({
13064
+ command: s.raw,
13065
+ success: s.success,
13066
+ data: s.data,
13067
+ duration: s.duration,
13068
+ ...s.hookOutputs?.length ? { hooks: s.hookOutputs } : {}
13069
+ }))
13070
+ },
13071
+ duration: chainResult.totalDuration
13072
+ },
13073
+ { command: "chain", totalSteps: chainResult.steps.length },
13074
+ mode
13075
+ );
12993
13076
  } else {
12994
13077
  printChainResult(chainResult);
12995
13078
  }
@@ -13056,7 +13139,15 @@ async function routeCommand(argvIn, stdinCommands) {
13056
13139
  const sessionName = options.session || process.env.XBROWSER_SESSION || "default";
13057
13140
  const cdpEndpoint = options.cdp || process.env.XBROWSER_CDP;
13058
13141
  if (options.version || options.v && positional.length === 0) {
13059
- console.log(`xbrowser v${version}`);
13142
+ if (mode === "json") {
13143
+ outputEnvelope(
13144
+ { success: true, data: { version, name: "@xbrowser/cli" } },
13145
+ { command: "version" },
13146
+ mode
13147
+ );
13148
+ } else {
13149
+ console.log(`xbrowser v${version}`);
13150
+ }
13060
13151
  return;
13061
13152
  }
13062
13153
  if (positional.length === 0) {
@@ -13252,18 +13343,23 @@ async function routeCommand(argvIn, stdinCommands) {
13252
13343
  if (isChainInput(fullInput)) {
13253
13344
  const chainResult = await executeChain(fullInput, { cdpEndpoint, sessionName });
13254
13345
  if (mode === "json" || mode === "yaml") {
13255
- const output = {
13256
- success: chainResult.success,
13257
- steps: chainResult.steps.map((s) => ({
13258
- command: s.raw,
13259
- success: s.success,
13260
- data: s.data,
13261
- duration: s.duration
13262
- })),
13263
- totalDuration: chainResult.totalDuration,
13264
- ...chainResult.stoppedReason ? { stoppedReason: chainResult.stoppedReason } : {}
13265
- };
13266
- outputResult(output, mode);
13346
+ outputEnvelope(
13347
+ {
13348
+ success: chainResult.success,
13349
+ data: {
13350
+ steps: chainResult.steps.map((s) => ({
13351
+ command: s.raw,
13352
+ success: s.success,
13353
+ data: s.data,
13354
+ duration: s.duration
13355
+ })),
13356
+ ...chainResult.stoppedReason ? { stoppedReason: chainResult.stoppedReason } : {}
13357
+ },
13358
+ duration: chainResult.totalDuration
13359
+ },
13360
+ { command: "chain", totalSteps: chainResult.steps.length },
13361
+ mode
13362
+ );
13267
13363
  if (!chainResult.success) throw new Error("Command failed");
13268
13364
  return;
13269
13365
  }
@@ -13408,6 +13504,17 @@ Run "xbrowser ${command} ${subCommand} --help" to see available parameters.`
13408
13504
  session = await createSession(sessionName, void 0, cdpEndpoint ? { cdpEndpoint } : {});
13409
13505
  }
13410
13506
  }
13507
+ const cmdTabIndex = options.tab !== void 0 ? Number(options.tab) : void 0;
13508
+ if (cmdTabIndex !== void 0 && session?.context) {
13509
+ const pages = session.context.pages();
13510
+ if (cmdTabIndex >= 0 && cmdTabIndex < pages.length) {
13511
+ const targetPage = pages[cmdTabIndex];
13512
+ await targetPage.bringToFront().catch(() => {
13513
+ });
13514
+ const { setActivePage: setActivePage2 } = await import("./browser-TLIDFFEG.js");
13515
+ setActivePage2(session, targetPage);
13516
+ }
13517
+ }
13411
13518
  const ctx = {
13412
13519
  args: cmdArgsForPlugin,
13413
13520
  options,
@@ -13485,22 +13592,17 @@ Run "xbrowser ${command} ${subCommand} --help" to see available parameters.`
13485
13592
  const outputData = isCommandResult2(result) ? result.data : result && typeof result === "object" ? result.data ?? result : result;
13486
13593
  const tips = isCommandResult2(result) ? result.tips : result && typeof result === "object" ? result.tips : void 0;
13487
13594
  if (mode === "json" || mode === "yaml") {
13488
- const finalOutput = {
13489
- data: outputData
13490
- };
13491
- if (injectedViewerUrl) {
13492
- finalOutput.viewerUrl = injectedViewerUrl;
13493
- }
13494
- if (tips?.length) {
13495
- finalOutput.tips = tips;
13496
- }
13497
- if (hookOutputs.length > 0) {
13498
- finalOutput.hooks = hookOutputs;
13499
- }
13500
- console.log(outputFormatter2.format(finalOutput, { mode, color: false, emoji: false }));
13501
- if (tips?.length) {
13502
- for (const tip of tips) console.error(`\u{1F4A1} ${typeof tip === "string" ? tip : tip.message}`);
13503
- }
13595
+ const resultSuccess = isCommandResult2(result) ? result.success !== false : true;
13596
+ const resultMsg = isCommandResult2(result) ? result.message : void 0;
13597
+ const duration = Date.now() - cmdStart;
13598
+ const envelopeMeta = { command: `${command} ${subCommand}` };
13599
+ if (injectedViewerUrl) envelopeMeta.viewerUrl = injectedViewerUrl;
13600
+ if (hookOutputs.length > 0) envelopeMeta.hooks = hookOutputs;
13601
+ outputEnvelope(
13602
+ { success: resultSuccess, data: outputData, message: resultMsg, tips, duration },
13603
+ envelopeMeta,
13604
+ mode
13605
+ );
13504
13606
  } else {
13505
13607
  console.log(outputFormatter2.format(outputData, { mode: "text", color: true, emoji: true }));
13506
13608
  if (tips?.length) {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  SessionRecorder
3
- } from "./chunk-XU5WOJIV.js";
3
+ } from "./chunk-RMYEHTLS.js";
4
4
  import "./chunk-OZKD3W4X.js";
5
5
  import "./chunk-KFQGP6VL.js";
6
6
  export {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  SessionRecorder
3
- } from "./chunk-BALOM2HW.js";
3
+ } from "./chunk-FL6DOSWV.js";
4
4
  import "./chunk-KFQGP6VL.js";
5
5
  export {
6
6
  SessionRecorder
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xbrowser/cli",
3
- "version": "1.8.5",
3
+ "version": "1.8.6",
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": {
@@ -84,7 +84,7 @@
84
84
  "prepare": "husky"
85
85
  },
86
86
  "dependencies": {
87
- "@dyyz1993/xcli-core": "^0.18.0",
87
+ "@dyyz1993/xcli-core": "^0.19.0",
88
88
  "@types/react-syntax-highlighter": "^15.5.13",
89
89
  "@types/turndown": "^5.0.6",
90
90
  "cheerio": "^1.2.0",