@xbrowser/cli 1.8.4 → 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.
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  SessionRecorder
4
- } from "./chunk-ACFE6PKF.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-35DOHDTA.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-KVTJQKBA.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-RTDGURIJ.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-KVTJQKBA.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-2SVQTI2O.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-6QGZN5U7.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-4PIVOYCW.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.