@xbrowser/cli 1.8.5 → 1.8.7

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
@@ -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 {
@@ -109,7 +109,7 @@ import {
109
109
  // src/executor.ts
110
110
  import {
111
111
  ok as ok25,
112
- fail as fail8,
112
+ fail as fail9,
113
113
  isCommandResult,
114
114
  CompositeStorage as CompositeStorage2,
115
115
  TipCollector as TipCollector2,
@@ -2356,7 +2356,7 @@ var scrapeCommand = registerCommand({
2356
2356
  scope: "project",
2357
2357
  selectorParams: ["selector"],
2358
2358
  parameters: z15.object({
2359
- url: z15.string(),
2359
+ url: z15.string().optional(),
2360
2360
  selector: z15.string().optional(),
2361
2361
  timeout: z15.number().default(3e4),
2362
2362
  format: z15.enum(["markdown", "html", "text"]).default("markdown"),
@@ -2369,10 +2369,14 @@ var scrapeCommand = registerCommand({
2369
2369
  const { context, page } = await createEphemeralContext(resolveLaunchOpts(ctx));
2370
2370
  const maxAttempts = p.retries + 1;
2371
2371
  try {
2372
+ const targetUrl = p.url || page.url();
2373
+ if (!targetUrl || targetUrl === "about:blank") {
2374
+ return fail3("URL is required. Provide a URL or connect to a browser (--cdp) with a non-blank page.");
2375
+ }
2372
2376
  let lastError;
2373
2377
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
2374
2378
  try {
2375
- await page.goto(p.url, { waitUntil: "commit", timeout: p.timeout });
2379
+ await page.goto(targetUrl, { waitUntil: "commit", timeout: p.timeout });
2376
2380
  await page.waitForSelector("body", { timeout: p.timeout }).catch(() => {
2377
2381
  });
2378
2382
  await page.waitForLoadState("networkidle", Math.min(p.timeout, 8e3)).catch(() => {
@@ -2433,7 +2437,7 @@ var scrapeCommand = registerCommand({
2433
2437
  return { url: location.href, title: document.title, navigation, tables, forms: forms.slice(0, 20), links: links.slice(0, 30), mainText };
2434
2438
  });
2435
2439
  try {
2436
- persistFromScrape(p.url, structured);
2440
+ persistFromScrape(targetUrl, structured);
2437
2441
  } catch {
2438
2442
  }
2439
2443
  if (p.mode === "smart") {
@@ -2534,7 +2538,7 @@ var scrapeCommand = registerCommand({
2534
2538
 
2535
2539
  // src/commands/map.ts
2536
2540
  import { z as z16 } from "zod";
2537
- import { ok as ok16 } from "@dyyz1993/xcli-core";
2541
+ import { ok as ok16, fail as fail4 } from "@dyyz1993/xcli-core";
2538
2542
 
2539
2543
  // src/utils/url.ts
2540
2544
  var SKIP_EXTENSIONS = /* @__PURE__ */ new Set([
@@ -2770,7 +2774,7 @@ var mapCommand = registerCommand({
2770
2774
  description: "Discover all URLs on a website via sitemap and page link extraction",
2771
2775
  scope: "project",
2772
2776
  parameters: z16.object({
2773
- url: z16.string(),
2777
+ url: z16.string().optional(),
2774
2778
  search: z16.string().optional(),
2775
2779
  sitemap: z16.enum(["include", "only"]).optional(),
2776
2780
  includeSubdomains: z16.boolean().optional(),
@@ -2781,7 +2785,11 @@ var mapCommand = registerCommand({
2781
2785
  handler: async (p, ctx) => {
2782
2786
  const { context, page } = await createEphemeralContext(resolveLaunchOpts(ctx));
2783
2787
  try {
2784
- const links = await discoverUrls(page, p.url, {
2788
+ const targetUrl = p.url || page.url();
2789
+ if (!targetUrl || targetUrl === "about:blank") {
2790
+ return fail4("URL is required. Provide a URL or connect to a browser (--cdp) with a non-blank page.");
2791
+ }
2792
+ const links = await discoverUrls(page, targetUrl, {
2785
2793
  sitemap: p.sitemap,
2786
2794
  includeSubdomains: p.includeSubdomains,
2787
2795
  allowExternalLinks: p.allowExternalLinks,
@@ -3624,7 +3632,7 @@ ${errors.map((e) => ` - ${e.engine}: ${e.error}`).join("\n")}`
3624
3632
 
3625
3633
  // src/commands/network.ts
3626
3634
  import { z as z19 } from "zod";
3627
- import { ok as ok19, fail as fail4 } from "@dyyz1993/xcli-core";
3635
+ import { ok as ok19, fail as fail5 } from "@dyyz1993/xcli-core";
3628
3636
  function extractPath2(url) {
3629
3637
  try {
3630
3638
  const u = new URL(url);
@@ -3789,7 +3797,7 @@ var networkCommand = registerCommand({
3789
3797
  };
3790
3798
  if (p.listen) {
3791
3799
  const page2 = ctx.page;
3792
- if (!page2) return fail4("No active page. Use --cdp to connect first.");
3800
+ if (!page2) return fail5("No active page. Use --cdp to connect first.");
3793
3801
  const captures = [];
3794
3802
  const consoleMessages = [];
3795
3803
  const wsCaptures = [];
@@ -4446,7 +4454,7 @@ function parseMarkdownResults(rawText) {
4446
4454
 
4447
4455
  // src/commands/snapshot.ts
4448
4456
  import { z as z21 } from "zod";
4449
- import { ok as ok20, fail as fail5, normalizeTips as normalizeTips3 } from "@dyyz1993/xcli-core";
4457
+ import { ok as ok20, fail as fail6, normalizeTips as normalizeTips3 } from "@dyyz1993/xcli-core";
4450
4458
 
4451
4459
  // src/runtime/ref-store.ts
4452
4460
  var sessions = /* @__PURE__ */ new Map();
@@ -5417,7 +5425,7 @@ var snapshotCommand = registerCommand({
5417
5425
  persistSemantics(url, aria);
5418
5426
  return ok20({ url, title, aria, text, dom }, normalizeTips3(tips));
5419
5427
  }
5420
- return fail5(`Unknown snapshot type: ${p.type}`);
5428
+ return fail6(`Unknown snapshot type: ${p.type}`);
5421
5429
  }
5422
5430
  });
5423
5431
  function persistSemantics(url, aria) {
@@ -5586,7 +5594,7 @@ var waitForCommand = registerCommand({
5586
5594
 
5587
5595
  // src/commands/tab.ts
5588
5596
  import { z as z23 } from "zod";
5589
- import { ok as ok22, fail as fail6 } from "@dyyz1993/xcli-core";
5597
+ import { ok as ok22, fail as fail7 } from "@dyyz1993/xcli-core";
5590
5598
  var TabParams = z23.object({
5591
5599
  subcommand: z23.enum(["list", "new", "close", "switch"]),
5592
5600
  url: z23.string().optional(),
@@ -5603,7 +5611,7 @@ var tabCommand = registerCommand({
5603
5611
  }),
5604
5612
  handler: async (p, ctx) => {
5605
5613
  if (!ctx.browserContext) {
5606
- return fail6("No browser context available. Use --cdp to connect to a browser first.");
5614
+ return fail7("No browser context available. Use --cdp to connect to a browser first.");
5607
5615
  }
5608
5616
  const pages = ctx.browserContext.pages();
5609
5617
  switch (p.subcommand) {
@@ -5616,7 +5624,7 @@ var tabCommand = registerCommand({
5616
5624
  case "switch":
5617
5625
  return handleSwitch(p, pages, ctx);
5618
5626
  default:
5619
- return fail6(`Unknown subcommand: ${p.subcommand}`);
5627
+ return fail7(`Unknown subcommand: ${p.subcommand}`);
5620
5628
  }
5621
5629
  }
5622
5630
  });
@@ -5673,11 +5681,11 @@ async function handleNew(p, _pages, ctx) {
5673
5681
  }
5674
5682
  async function handleClose(p, pages, ctx) {
5675
5683
  if (pages.length <= 1) {
5676
- return fail6("Cannot close the last remaining tab");
5684
+ return fail7("Cannot close the last remaining tab");
5677
5685
  }
5678
5686
  const closeIndex = p.index ?? pages.indexOf(ctx.page);
5679
5687
  if (closeIndex < 0 || closeIndex >= pages.length) {
5680
- return fail6(`Invalid tab index: ${closeIndex}. Valid range: 0-${pages.length - 1}`);
5688
+ return fail7(`Invalid tab index: ${closeIndex}. Valid range: 0-${pages.length - 1}`);
5681
5689
  }
5682
5690
  const pageToClose = pages[closeIndex];
5683
5691
  const isActivePage = pageToClose === ctx.page;
@@ -5700,10 +5708,10 @@ async function handleClose(p, pages, ctx) {
5700
5708
  }
5701
5709
  async function handleSwitch(p, pages, ctx) {
5702
5710
  if (p.index === void 0) {
5703
- return fail6("Parameter --index is required for switch subcommand");
5711
+ return fail7("Parameter --index is required for switch subcommand");
5704
5712
  }
5705
5713
  if (p.index < 0 || p.index >= pages.length) {
5706
- return fail6(`Invalid tab index: ${p.index}. Valid range: 0-${pages.length - 1}`);
5714
+ return fail7(`Invalid tab index: ${p.index}. Valid range: 0-${pages.length - 1}`);
5707
5715
  }
5708
5716
  const targetPage = pages[p.index];
5709
5717
  await targetPage.bringToFront().catch(() => {
@@ -5955,7 +5963,7 @@ registerCommandDefinition("addinitscript", ["script"]);
5955
5963
 
5956
5964
  // src/commands/find.ts
5957
5965
  import { z as z25 } from "zod";
5958
- import { ok as ok24, fail as fail7, normalizeTips as normalizeTips5 } from "@dyyz1993/xcli-core";
5966
+ import { ok as ok24, fail as fail8, normalizeTips as normalizeTips5 } from "@dyyz1993/xcli-core";
5959
5967
  var actionSchema2 = z25.enum(["click", "fill", "type", "select", "hover", "check"]);
5960
5968
  var findCommand = registerCommand({
5961
5969
  name: "find",
@@ -5996,7 +6004,7 @@ var findCommand = registerCommand({
5996
6004
  });
5997
6005
  const count = await locator.count();
5998
6006
  if (count === 0) {
5999
- return fail7(`No element found with ${p.strategy}="${p.value}"`);
6007
+ return fail8(`No element found with ${p.strategy}="${p.value}"`);
6000
6008
  }
6001
6009
  const tips = [];
6002
6010
  const target = selectTarget(locator, p.strategy);
@@ -6008,15 +6016,15 @@ var findCommand = registerCommand({
6008
6016
  await target.click({ timeout: p.timeout, force: true });
6009
6017
  return okWithTips({ matched: count, selector, action: "click" }, tips);
6010
6018
  } else if (actionName === "fill") {
6011
- if (actionValue === void 0) return fail7("find fill requires a value");
6019
+ if (actionValue === void 0) return fail8("find fill requires a value");
6012
6020
  await target.fill(actionValue, { timeout: p.timeout, force: true });
6013
6021
  return okWithTips({ matched: count, selector, action: `fill("${actionValue}")` }, tips);
6014
6022
  } else if (actionName === "type") {
6015
- if (actionValue === void 0) return fail7("find type requires a value");
6023
+ if (actionValue === void 0) return fail8("find type requires a value");
6016
6024
  await target.type(actionValue, { delay: 10, timeout: p.timeout });
6017
6025
  return okWithTips({ matched: count, selector, action: `type("${actionValue}")` }, tips);
6018
6026
  } else if (actionName === "select") {
6019
- if (actionValue === void 0) return fail7("find select requires a value");
6027
+ if (actionValue === void 0) return fail8("find select requires a value");
6020
6028
  await target.selectOption(actionValue);
6021
6029
  return okWithTips({ matched: count, selector, action: `select("${actionValue}")` }, tips);
6022
6030
  } else if (actionName === "hover") {
@@ -7452,7 +7460,7 @@ async function guardCheck(commandName) {
7452
7460
  }
7453
7461
  }
7454
7462
  function errorResult(message) {
7455
- return { ...fail8(message), duration: 0 };
7463
+ return { ...fail9(message), duration: 0 };
7456
7464
  }
7457
7465
  function tipsToMessages(tips) {
7458
7466
  if (!tips || tips.length === 0) return [];
@@ -7525,9 +7533,14 @@ async function executeCommand(commandName, params, sessionName = "default", extr
7525
7533
  const { _target: _u, ...rest } = params;
7526
7534
  params = rest;
7527
7535
  }
7536
+ const _tabIndex = params._tabIndex;
7537
+ if (_tabIndex !== void 0) {
7538
+ const { _tabIndex: _u, ...rest } = params;
7539
+ params = rest;
7540
+ }
7528
7541
  let targetPageOverride = null;
7529
7542
  if (_target && extraOpts?.cdpEndpoint) {
7530
- const { findTargetPage } = await import("./browser-PTELW3WS.js");
7543
+ const { findTargetPage } = await import("./browser-TLIDFFEG.js");
7531
7544
  targetPageOverride = await findTargetPage(extraOpts.cdpEndpoint, _target);
7532
7545
  if (!targetPageOverride) {
7533
7546
  return errorResult(`Target "${_target}" not found. Use 'xbrowser targets --cdp ${extraOpts.cdpEndpoint}' to list available pages.`);
@@ -7599,6 +7612,16 @@ async function executeCommand(commandName, params, sessionName = "default", extr
7599
7612
  cliName: "xbrowser",
7600
7613
  tips: new TipCollector2()
7601
7614
  };
7615
+ if (_tabIndex !== void 0 && session?.context) {
7616
+ const pages = session.context.pages();
7617
+ if (_tabIndex >= 0 && _tabIndex < pages.length) {
7618
+ const targetPage = pages[_tabIndex];
7619
+ await targetPage.bringToFront().catch(() => {
7620
+ });
7621
+ setActivePage(session, targetPage);
7622
+ ctx.page = targetPage;
7623
+ }
7624
+ }
7602
7625
  const start = Date.now();
7603
7626
  if (session) {
7604
7627
  streamCommandEvent(session.id, {
@@ -7627,7 +7650,17 @@ async function executeCommand(commandName, params, sessionName = "default", extr
7627
7650
  if (hooks.length > 0 && session?.page) {
7628
7651
  await Promise.all(hooks.map((h) => h.onBeforeCommand?.({ page: session.page, command: commandName, params })));
7629
7652
  }
7630
- const raw = await command.handler(params, ctx);
7653
+ let raw;
7654
+ const handlerPromise = command.handler(params, ctx);
7655
+ if (extraOpts?.timeout && extraOpts.timeout > 0) {
7656
+ const timeoutMs = extraOpts.timeout;
7657
+ const timeoutPromise = new Promise(
7658
+ (_, reject) => setTimeout(() => reject(new Error(`Command timed out after ${timeoutMs}ms`)), timeoutMs)
7659
+ );
7660
+ raw = await Promise.race([handlerPromise, timeoutPromise]);
7661
+ } else {
7662
+ raw = await handlerPromise;
7663
+ }
7631
7664
  const end = Date.now();
7632
7665
  const duration = end - start;
7633
7666
  let hookOutputs;
@@ -7731,7 +7764,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
7731
7764
  duration,
7732
7765
  timestamp: start
7733
7766
  });
7734
- return { ...fail8(errorMessage), duration };
7767
+ return { ...fail9(errorMessage), duration };
7735
7768
  } finally {
7736
7769
  }
7737
7770
  }
@@ -7772,7 +7805,7 @@ async function executeChain(input, options) {
7772
7805
  results.push({
7773
7806
  command: cmdName,
7774
7807
  raw: cmdStr,
7775
- ...fail8(`Plugin "${cmdName}" requires a sub-command`),
7808
+ ...fail9(`Plugin "${cmdName}" requires a sub-command`),
7776
7809
  duration: 0
7777
7810
  });
7778
7811
  if (type === "and") {
@@ -7791,7 +7824,7 @@ async function executeChain(input, options) {
7791
7824
  results.push({
7792
7825
  command: cmdName,
7793
7826
  raw: cmdStr,
7794
- ...fail8(`Unknown command "${subCommand}" for plugin "${cmdName}"`),
7827
+ ...fail9(`Unknown command "${subCommand}" for plugin "${cmdName}"`),
7795
7828
  duration: 0
7796
7829
  });
7797
7830
  if (type === "and") {
@@ -7923,7 +7956,7 @@ async function executeChain(input, options) {
7923
7956
  results.push({
7924
7957
  command: `${cmdName} ${subCommand}`,
7925
7958
  raw: cmdStr,
7926
- ...fail8(errorMessage),
7959
+ ...fail9(errorMessage),
7927
7960
  duration: duration2
7928
7961
  });
7929
7962
  if (type === "and") {
@@ -8201,16 +8234,29 @@ var configBuiltin = {
8201
8234
  execute: async (args, _options, _ctx) => {
8202
8235
  const [subcommand, ...rest] = args;
8203
8236
  if (!subcommand || subcommand === "list") {
8237
+ let flatten2 = function(obj, prefix = "") {
8238
+ const entries2 = [];
8239
+ for (const [k, v] of Object.entries(obj)) {
8240
+ const fullKey = prefix ? `${prefix}.${k}` : k;
8241
+ if (v && typeof v === "object" && !Array.isArray(v)) {
8242
+ entries2.push(...flatten2(v, fullKey));
8243
+ } else {
8244
+ entries2.push({ key: fullKey, value: v });
8245
+ }
8246
+ }
8247
+ return entries2;
8248
+ };
8249
+ var flatten = flatten2;
8204
8250
  const config = loadConfig();
8205
- const keys = Object.keys(config);
8206
- if (keys.length === 0) {
8251
+ const entries = flatten2(config);
8252
+ if (entries.length === 0) {
8207
8253
  console.log("Configuration is empty");
8208
8254
  return;
8209
8255
  }
8210
8256
  console.log("Configuration:");
8211
8257
  console.log("");
8212
- for (const k of keys) {
8213
- console.log(` ${k} = ${config[k]}`);
8258
+ for (const { key, value } of entries) {
8259
+ console.log(` ${key} = ${value}`);
8214
8260
  }
8215
8261
  return;
8216
8262
  }
@@ -9009,6 +9055,39 @@ function outputError(message) {
9009
9055
  console.error(formatted);
9010
9056
  process.exit(1);
9011
9057
  }
9058
+ function outputEnvelope(result, meta, mode) {
9059
+ if (mode !== "json" && mode !== "yaml") {
9060
+ if (!result.success) {
9061
+ outputError(result.message || "Unknown error");
9062
+ return;
9063
+ }
9064
+ outputResult(result.data, mode);
9065
+ return;
9066
+ }
9067
+ const { command, ...extraMeta } = meta;
9068
+ const commandResult = {
9069
+ success: result.success,
9070
+ data: result.data,
9071
+ message: result.message,
9072
+ tips: [],
9073
+ meta: {
9074
+ duration: result.duration ?? 0,
9075
+ ...extraMeta
9076
+ }
9077
+ };
9078
+ const formatted = outputFormatter.formatEnvelope(commandResult, {
9079
+ command,
9080
+ extraMeta,
9081
+ mode
9082
+ });
9083
+ console.log(formatted);
9084
+ if (result.tips?.length) {
9085
+ for (const tip of result.tips) {
9086
+ const text = typeof tip === "string" ? tip : tip.message;
9087
+ if (text) console.error(` \u{1F4A1} ${text}`);
9088
+ }
9089
+ }
9090
+ }
9012
9091
 
9013
9092
  // src/builtins/plugin.ts
9014
9093
  var pluginLoader2 = null;
@@ -10573,9 +10652,13 @@ async function handleBrowserCommand(command, args, options, sessionName, mode, c
10573
10652
  if (target) {
10574
10653
  params = { ...params, _target: target };
10575
10654
  }
10655
+ const tabIndex = options.tab;
10656
+ if (tabIndex !== void 0) {
10657
+ params = { ...params, _tabIndex: Number(tabIndex) };
10658
+ }
10576
10659
  const result = cdpEndpoint ? await executeCommand(cmdName, params, sessionName, { cdpEndpoint }) : await executeCommand(cmdName, params, sessionName);
10577
10660
  if (mode === "json" || mode === "yaml") {
10578
- outputResult(result, mode);
10661
+ outputEnvelope(result, { command: cmdName }, mode);
10579
10662
  } else if (!result.success) {
10580
10663
  outputError(result.message || "Command failed");
10581
10664
  } else {
@@ -10635,7 +10718,7 @@ async function handleSession(args, options, mode, _cdpEndpoint) {
10635
10718
  }
10636
10719
  } catch {
10637
10720
  }
10638
- outputResult({ ok: true, closed: count, all: true }, mode);
10721
+ outputEnvelope({ success: true, data: { closed: count, all: true } }, { command: "session close" }, mode);
10639
10722
  } else {
10640
10723
  const name = options.session || options.name || process.env.XBROWSER_SESSION || "default";
10641
10724
  try {
@@ -10643,7 +10726,7 @@ async function handleSession(args, options, mode, _cdpEndpoint) {
10643
10726
  } catch {
10644
10727
  }
10645
10728
  await closeSession(name);
10646
- outputResult({ ok: true, name }, mode);
10729
+ outputEnvelope({ success: true, data: { name } }, { command: "session close" }, mode);
10647
10730
  }
10648
10731
  break;
10649
10732
  }
@@ -10651,10 +10734,10 @@ async function handleSession(args, options, mode, _cdpEndpoint) {
10651
10734
  case "ls": {
10652
10735
  try {
10653
10736
  const sessions2 = await forwardSessionList();
10654
- outputResult({ sessions: sessions2 }, mode);
10737
+ outputEnvelope({ success: true, data: { sessions: sessions2 } }, { command: "session list" }, mode);
10655
10738
  } catch {
10656
10739
  const sessions2 = await listSessions();
10657
- outputResult({ sessions: sessions2 }, mode);
10740
+ outputEnvelope({ success: true, data: { sessions: sessions2 } }, { command: "session list" }, mode);
10658
10741
  }
10659
10742
  break;
10660
10743
  }
@@ -10669,7 +10752,7 @@ async function handleSession(args, options, mode, _cdpEndpoint) {
10669
10752
  await stopDaemonProcess();
10670
10753
  } catch {
10671
10754
  }
10672
- outputResult({ ok: true, name, killed: true, daemon: "stopped" }, mode);
10755
+ outputEnvelope({ success: true, data: { name, killed: true, daemon: "stopped" } }, { command: "session kill" }, mode);
10673
10756
  break;
10674
10757
  }
10675
10758
  case "kill-all": {
@@ -10688,7 +10771,7 @@ async function handleSession(args, options, mode, _cdpEndpoint) {
10688
10771
  } catch {
10689
10772
  }
10690
10773
  const cleaned = cleanSessionFiles();
10691
- outputResult({ ok: true, sessionsCleaned: cleaned, daemon: "killed" }, mode);
10774
+ outputEnvelope({ success: true, data: { sessionsCleaned: cleaned, daemon: "killed" } }, { command: "session kill-all" }, mode);
10692
10775
  break;
10693
10776
  }
10694
10777
  default:
@@ -10808,7 +10891,7 @@ async function handleSearch(args, options, mode) {
10808
10891
  }
10809
10892
  }
10810
10893
  if (mode === "json") {
10811
- outputResult({ results, total: results.length }, mode);
10894
+ outputEnvelope({ success: true, data: { results, total: results.length } }, { command: "plugin search" }, mode);
10812
10895
  } else {
10813
10896
  if (results.length === 0) {
10814
10897
  console.log("No plugins found");
@@ -10837,7 +10920,7 @@ async function handlePluginInfo(args, options, mode) {
10837
10920
  if (pluginInfo) {
10838
10921
  const d = pluginInfo;
10839
10922
  if (mode === "json") {
10840
- outputResult({ source: "marketplace", ...d }, mode);
10923
+ outputEnvelope({ success: true, data: { source: "marketplace", ...d } }, { command: "plugin info" }, mode);
10841
10924
  return;
10842
10925
  }
10843
10926
  console.log(`\u540D\u79F0: ${d.name || ""}`);
@@ -10863,7 +10946,7 @@ async function handlePluginInfo(args, options, mode) {
10863
10946
  const pkg = latest && versions?.[latest];
10864
10947
  if (pkg) {
10865
10948
  if (mode === "json") {
10866
- outputResult({ source: "npm", name: pkg.name, version: latest, description: pkg.description }, mode);
10949
+ outputEnvelope({ success: true, data: { source: "npm", name: pkg.name, version: latest, description: pkg.description } }, { command: "plugin info" }, mode);
10867
10950
  return;
10868
10951
  }
10869
10952
  console.log(`\u540D\u79F0: ${pkg.name || ""}`);
@@ -10892,7 +10975,7 @@ async function handlePluginSchema(args, mode) {
10892
10975
  return;
10893
10976
  }
10894
10977
  if (mode === "json") {
10895
- outputResult(contract, mode);
10978
+ outputEnvelope({ success: true, data: contract }, { command: "plugin schema" }, mode);
10896
10979
  return;
10897
10980
  }
10898
10981
  if ("commands" in contract) {
@@ -10970,8 +11053,9 @@ async function handlePlugin(args, options, mode) {
10970
11053
  }
10971
11054
  } catch {
10972
11055
  }
10973
- outputResult(
10974
- { ok: true, name: result.name, source: result.source, path: result.path },
11056
+ outputEnvelope(
11057
+ { success: true, data: { name: result.name, source: result.source, path: result.path } },
11058
+ { command: "plugin install" },
10975
11059
  mode
10976
11060
  );
10977
11061
  break;
@@ -10990,7 +11074,7 @@ async function handlePlugin(args, options, mode) {
10990
11074
  await loader.reloadPlugin(name);
10991
11075
  } catch {
10992
11076
  }
10993
- outputResult({ ok: true, name }, mode);
11077
+ outputEnvelope({ success: true, data: { name } }, { command: "plugin uninstall" }, mode);
10994
11078
  break;
10995
11079
  }
10996
11080
  case "list": {
@@ -11012,7 +11096,7 @@ async function handlePlugin(args, options, mode) {
11012
11096
  };
11013
11097
  });
11014
11098
  if (mode === "json") {
11015
- outputResult({ plugins: enrichedPlugins }, mode);
11099
+ outputEnvelope({ success: true, data: { plugins: enrichedPlugins } }, { command: "plugin list" }, mode);
11016
11100
  } else {
11017
11101
  if (enrichedPlugins.length === 0) {
11018
11102
  console.log("No plugins installed");
@@ -11048,7 +11132,7 @@ Total: ${enrichedPlugins.length} plugins`);
11048
11132
  } catch {
11049
11133
  outputError(`Plugin "${name}" not found. Use 'xbrowser plugin list' to see installed plugins.`);
11050
11134
  }
11051
- outputResult({ ok: true, name }, mode);
11135
+ outputEnvelope({ success: true, data: { name } }, { command: "plugin reload" }, mode);
11052
11136
  break;
11053
11137
  }
11054
11138
  case "search":
@@ -11080,24 +11164,22 @@ function handleDaemon(args, options, mode) {
11080
11164
  case "start": {
11081
11165
  const port = options.port ? Number(options.port) : 9224;
11082
11166
  startDaemonProcess(port).then(
11083
- (config) => outputResult({ ok: true, pid: config.pid, port: config.port }, mode)
11167
+ (config) => outputEnvelope({ success: true, data: { pid: config.pid, port: config.port } }, { command: "daemon start" }, mode)
11084
11168
  ).catch(
11085
11169
  (e) => outputError(e instanceof Error ? e.message : String(e))
11086
11170
  );
11087
11171
  break;
11088
11172
  }
11089
11173
  case "stop": {
11090
- stopDaemonProcess().then(() => outputResult({ ok: true }, mode)).catch(
11174
+ stopDaemonProcess().then(() => outputEnvelope({ success: true, data: {} }, { command: "daemon stop" }, mode)).catch(
11091
11175
  (e) => outputError(e instanceof Error ? e.message : String(e))
11092
11176
  );
11093
11177
  break;
11094
11178
  }
11095
11179
  case "status": {
11096
11180
  const status = getDaemonProcessStatus();
11097
- outputResult(
11098
- status.running ? { running: true, pid: status.pid, port: status.port } : { running: false },
11099
- mode
11100
- );
11181
+ const statusData = status.running ? { running: true, pid: status.pid, port: status.port } : { running: false };
11182
+ outputEnvelope({ success: true, data: statusData }, { command: "daemon status" }, mode);
11101
11183
  break;
11102
11184
  }
11103
11185
  default:
@@ -11475,7 +11557,7 @@ async function handleFilter(args, _mode, options) {
11475
11557
  console.log(` Original: ${result.originalCount}, After: ${result.filteredCount}, Removed: ${result.removed} (${result.percentage}%)`);
11476
11558
  }
11477
11559
  async function handleGeneratePlugin(sessionName, pluginName, outputDir) {
11478
- const { SessionRecorder: SessionRecorder2 } = await import("./session-recorder-H2WPBCSW.js");
11560
+ const { SessionRecorder: SessionRecorder2 } = await import("./session-recorder-33UKWCIR.js");
11479
11561
  const { readSiteKnowledge: readSiteKnowledge2, toMarkdown } = await import("./site-knowledge-SYC6VCDB.js");
11480
11562
  const { mkdirSync: mkdirSync11, writeFileSync: writeFileSync13 } = await import("fs");
11481
11563
  const { join: join14 } = await import("path");
@@ -12822,7 +12904,8 @@ var KNOWN_GLOBAL_OPTIONS = /* @__PURE__ */ new Set([
12822
12904
  "port",
12823
12905
  "token",
12824
12906
  "timeout",
12825
- "headless"
12907
+ "headless",
12908
+ "tab"
12826
12909
  ]);
12827
12910
  function showCommandHelp(siteName, cmd, siteConfig, mode) {
12828
12911
  const c = cmd;
@@ -12976,20 +13059,28 @@ async function handleEvalMode(argv) {
12976
13059
  }
12977
13060
  async function handleChainInput(input, argv) {
12978
13061
  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;
13062
+ const hasJson = argv ? argv.some((a) => a === "--json" || a.startsWith("--json=") || a.includes(" --json") || a.startsWith("--json")) || argv.includes("-j") : false;
13063
+ const hasYaml = argv ? argv.some((a) => a === "--yaml" || a.startsWith("--yaml=")) : false;
13064
+ const mode = hasJson ? "json" : hasYaml ? "yaml" : "text";
12980
13065
  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));
13066
+ if (mode === "json" || mode === "yaml") {
13067
+ outputEnvelope(
13068
+ {
13069
+ success: chainResult.success,
13070
+ data: {
13071
+ steps: chainResult.steps.map((s) => ({
13072
+ command: s.raw,
13073
+ success: s.success,
13074
+ data: s.data,
13075
+ duration: s.duration,
13076
+ ...s.hookOutputs?.length ? { hooks: s.hookOutputs } : {}
13077
+ }))
13078
+ },
13079
+ duration: chainResult.totalDuration
13080
+ },
13081
+ { command: "chain", totalSteps: chainResult.steps.length },
13082
+ mode
13083
+ );
12993
13084
  } else {
12994
13085
  printChainResult(chainResult);
12995
13086
  }
@@ -13056,7 +13147,15 @@ async function routeCommand(argvIn, stdinCommands) {
13056
13147
  const sessionName = options.session || process.env.XBROWSER_SESSION || "default";
13057
13148
  const cdpEndpoint = options.cdp || process.env.XBROWSER_CDP;
13058
13149
  if (options.version || options.v && positional.length === 0) {
13059
- console.log(`xbrowser v${version}`);
13150
+ if (mode === "json") {
13151
+ outputEnvelope(
13152
+ { success: true, data: { version, name: "@xbrowser/cli" } },
13153
+ { command: "version" },
13154
+ mode
13155
+ );
13156
+ } else {
13157
+ console.log(`xbrowser v${version}`);
13158
+ }
13060
13159
  return;
13061
13160
  }
13062
13161
  if (positional.length === 0) {
@@ -13252,18 +13351,23 @@ async function routeCommand(argvIn, stdinCommands) {
13252
13351
  if (isChainInput(fullInput)) {
13253
13352
  const chainResult = await executeChain(fullInput, { cdpEndpoint, sessionName });
13254
13353
  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);
13354
+ outputEnvelope(
13355
+ {
13356
+ success: chainResult.success,
13357
+ data: {
13358
+ steps: chainResult.steps.map((s) => ({
13359
+ command: s.raw,
13360
+ success: s.success,
13361
+ data: s.data,
13362
+ duration: s.duration
13363
+ })),
13364
+ ...chainResult.stoppedReason ? { stoppedReason: chainResult.stoppedReason } : {}
13365
+ },
13366
+ duration: chainResult.totalDuration
13367
+ },
13368
+ { command: "chain", totalSteps: chainResult.steps.length },
13369
+ mode
13370
+ );
13267
13371
  if (!chainResult.success) throw new Error("Command failed");
13268
13372
  return;
13269
13373
  }
@@ -13408,6 +13512,17 @@ Run "xbrowser ${command} ${subCommand} --help" to see available parameters.`
13408
13512
  session = await createSession(sessionName, void 0, cdpEndpoint ? { cdpEndpoint } : {});
13409
13513
  }
13410
13514
  }
13515
+ const cmdTabIndex = options.tab !== void 0 ? Number(options.tab) : void 0;
13516
+ if (cmdTabIndex !== void 0 && session?.context) {
13517
+ const pages = session.context.pages();
13518
+ if (cmdTabIndex >= 0 && cmdTabIndex < pages.length) {
13519
+ const targetPage = pages[cmdTabIndex];
13520
+ await targetPage.bringToFront().catch(() => {
13521
+ });
13522
+ const { setActivePage: setActivePage2 } = await import("./browser-TLIDFFEG.js");
13523
+ setActivePage2(session, targetPage);
13524
+ }
13525
+ }
13411
13526
  const ctx = {
13412
13527
  args: cmdArgsForPlugin,
13413
13528
  options,
@@ -13485,22 +13600,17 @@ Run "xbrowser ${command} ${subCommand} --help" to see available parameters.`
13485
13600
  const outputData = isCommandResult2(result) ? result.data : result && typeof result === "object" ? result.data ?? result : result;
13486
13601
  const tips = isCommandResult2(result) ? result.tips : result && typeof result === "object" ? result.tips : void 0;
13487
13602
  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
- }
13603
+ const resultSuccess = isCommandResult2(result) ? result.success !== false : true;
13604
+ const resultMsg = isCommandResult2(result) ? result.message : void 0;
13605
+ const duration = Date.now() - cmdStart;
13606
+ const envelopeMeta = { command: `${command} ${subCommand}` };
13607
+ if (injectedViewerUrl) envelopeMeta.viewerUrl = injectedViewerUrl;
13608
+ if (hookOutputs.length > 0) envelopeMeta.hooks = hookOutputs;
13609
+ outputEnvelope(
13610
+ { success: resultSuccess, data: outputData, message: resultMsg, tips, duration },
13611
+ envelopeMeta,
13612
+ mode
13613
+ );
13504
13614
  } else {
13505
13615
  console.log(outputFormatter2.format(outputData, { mode: "text", color: true, emoji: true }));
13506
13616
  if (tips?.length) {