@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/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,
@@ -182,7 +182,7 @@ function asZodSchema(value) {
182
182
  // src/executor.ts
183
183
  import {
184
184
  ok as ok25,
185
- fail as fail8,
185
+ fail as fail9,
186
186
  isCommandResult,
187
187
  CompositeStorage as CompositeStorage2,
188
188
  TipCollector as TipCollector2,
@@ -2316,7 +2316,7 @@ var scrapeCommand = registerCommand({
2316
2316
  scope: "project",
2317
2317
  selectorParams: ["selector"],
2318
2318
  parameters: z15.object({
2319
- url: z15.string(),
2319
+ url: z15.string().optional(),
2320
2320
  selector: z15.string().optional(),
2321
2321
  timeout: z15.number().default(3e4),
2322
2322
  format: z15.enum(["markdown", "html", "text"]).default("markdown"),
@@ -2329,10 +2329,14 @@ var scrapeCommand = registerCommand({
2329
2329
  const { context, page } = await createEphemeralContext(resolveLaunchOpts(ctx));
2330
2330
  const maxAttempts = p.retries + 1;
2331
2331
  try {
2332
+ const targetUrl = p.url || page.url();
2333
+ if (!targetUrl || targetUrl === "about:blank") {
2334
+ return fail3("URL is required. Provide a URL or connect to a browser (--cdp) with a non-blank page.");
2335
+ }
2332
2336
  let lastError;
2333
2337
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
2334
2338
  try {
2335
- await page.goto(p.url, { waitUntil: "commit", timeout: p.timeout });
2339
+ await page.goto(targetUrl, { waitUntil: "commit", timeout: p.timeout });
2336
2340
  await page.waitForSelector("body", { timeout: p.timeout }).catch(() => {
2337
2341
  });
2338
2342
  await page.waitForLoadState("networkidle", Math.min(p.timeout, 8e3)).catch(() => {
@@ -2393,7 +2397,7 @@ var scrapeCommand = registerCommand({
2393
2397
  return { url: location.href, title: document.title, navigation, tables, forms: forms.slice(0, 20), links: links.slice(0, 30), mainText };
2394
2398
  });
2395
2399
  try {
2396
- persistFromScrape(p.url, structured);
2400
+ persistFromScrape(targetUrl, structured);
2397
2401
  } catch {
2398
2402
  }
2399
2403
  if (p.mode === "smart") {
@@ -2494,7 +2498,7 @@ var scrapeCommand = registerCommand({
2494
2498
 
2495
2499
  // src/commands/map.ts
2496
2500
  import { z as z16 } from "zod";
2497
- import { ok as ok16 } from "@dyyz1993/xcli-core";
2501
+ import { ok as ok16, fail as fail4 } from "@dyyz1993/xcli-core";
2498
2502
 
2499
2503
  // src/utils/url.ts
2500
2504
  var SKIP_EXTENSIONS = /* @__PURE__ */ new Set([
@@ -2730,7 +2734,7 @@ var mapCommand = registerCommand({
2730
2734
  description: "Discover all URLs on a website via sitemap and page link extraction",
2731
2735
  scope: "project",
2732
2736
  parameters: z16.object({
2733
- url: z16.string(),
2737
+ url: z16.string().optional(),
2734
2738
  search: z16.string().optional(),
2735
2739
  sitemap: z16.enum(["include", "only"]).optional(),
2736
2740
  includeSubdomains: z16.boolean().optional(),
@@ -2741,7 +2745,11 @@ var mapCommand = registerCommand({
2741
2745
  handler: async (p, ctx) => {
2742
2746
  const { context, page } = await createEphemeralContext(resolveLaunchOpts(ctx));
2743
2747
  try {
2744
- const links = await discoverUrls(page, p.url, {
2748
+ const targetUrl = p.url || page.url();
2749
+ if (!targetUrl || targetUrl === "about:blank") {
2750
+ return fail4("URL is required. Provide a URL or connect to a browser (--cdp) with a non-blank page.");
2751
+ }
2752
+ const links = await discoverUrls(page, targetUrl, {
2745
2753
  sitemap: p.sitemap,
2746
2754
  includeSubdomains: p.includeSubdomains,
2747
2755
  allowExternalLinks: p.allowExternalLinks,
@@ -3584,7 +3592,7 @@ ${errors.map((e) => ` - ${e.engine}: ${e.error}`).join("\n")}`
3584
3592
 
3585
3593
  // src/commands/network.ts
3586
3594
  import { z as z19 } from "zod";
3587
- import { ok as ok19, fail as fail4 } from "@dyyz1993/xcli-core";
3595
+ import { ok as ok19, fail as fail5 } from "@dyyz1993/xcli-core";
3588
3596
  function extractPath2(url) {
3589
3597
  try {
3590
3598
  const u = new URL(url);
@@ -3749,7 +3757,7 @@ var networkCommand = registerCommand({
3749
3757
  };
3750
3758
  if (p.listen) {
3751
3759
  const page2 = ctx.page;
3752
- if (!page2) return fail4("No active page. Use --cdp to connect first.");
3760
+ if (!page2) return fail5("No active page. Use --cdp to connect first.");
3753
3761
  const captures = [];
3754
3762
  const consoleMessages = [];
3755
3763
  const wsCaptures = [];
@@ -4129,7 +4137,7 @@ var ENGINE_KEY_ENUM = z20.enum(ALL_ENGINE_KEYS);
4129
4137
 
4130
4138
  // src/commands/snapshot.ts
4131
4139
  import { z as z21 } from "zod";
4132
- import { ok as ok20, fail as fail5, normalizeTips as normalizeTips3 } from "@dyyz1993/xcli-core";
4140
+ import { ok as ok20, fail as fail6, normalizeTips as normalizeTips3 } from "@dyyz1993/xcli-core";
4133
4141
 
4134
4142
  // src/runtime/ref-store.ts
4135
4143
  var sessions = /* @__PURE__ */ new Map();
@@ -5100,7 +5108,7 @@ var snapshotCommand = registerCommand({
5100
5108
  persistSemantics(url, aria);
5101
5109
  return ok20({ url, title, aria, text, dom }, normalizeTips3(tips));
5102
5110
  }
5103
- return fail5(`Unknown snapshot type: ${p.type}`);
5111
+ return fail6(`Unknown snapshot type: ${p.type}`);
5104
5112
  }
5105
5113
  });
5106
5114
  function persistSemantics(url, aria) {
@@ -5269,7 +5277,7 @@ var waitForCommand = registerCommand({
5269
5277
 
5270
5278
  // src/commands/tab.ts
5271
5279
  import { z as z23 } from "zod";
5272
- import { ok as ok22, fail as fail6 } from "@dyyz1993/xcli-core";
5280
+ import { ok as ok22, fail as fail7 } from "@dyyz1993/xcli-core";
5273
5281
  var TabParams = z23.object({
5274
5282
  subcommand: z23.enum(["list", "new", "close", "switch"]),
5275
5283
  url: z23.string().optional(),
@@ -5286,7 +5294,7 @@ var tabCommand = registerCommand({
5286
5294
  }),
5287
5295
  handler: async (p, ctx) => {
5288
5296
  if (!ctx.browserContext) {
5289
- return fail6("No browser context available. Use --cdp to connect to a browser first.");
5297
+ return fail7("No browser context available. Use --cdp to connect to a browser first.");
5290
5298
  }
5291
5299
  const pages = ctx.browserContext.pages();
5292
5300
  switch (p.subcommand) {
@@ -5299,7 +5307,7 @@ var tabCommand = registerCommand({
5299
5307
  case "switch":
5300
5308
  return handleSwitch(p, pages, ctx);
5301
5309
  default:
5302
- return fail6(`Unknown subcommand: ${p.subcommand}`);
5310
+ return fail7(`Unknown subcommand: ${p.subcommand}`);
5303
5311
  }
5304
5312
  }
5305
5313
  });
@@ -5356,11 +5364,11 @@ async function handleNew(p, _pages, ctx) {
5356
5364
  }
5357
5365
  async function handleClose(p, pages, ctx) {
5358
5366
  if (pages.length <= 1) {
5359
- return fail6("Cannot close the last remaining tab");
5367
+ return fail7("Cannot close the last remaining tab");
5360
5368
  }
5361
5369
  const closeIndex = p.index ?? pages.indexOf(ctx.page);
5362
5370
  if (closeIndex < 0 || closeIndex >= pages.length) {
5363
- return fail6(`Invalid tab index: ${closeIndex}. Valid range: 0-${pages.length - 1}`);
5371
+ return fail7(`Invalid tab index: ${closeIndex}. Valid range: 0-${pages.length - 1}`);
5364
5372
  }
5365
5373
  const pageToClose = pages[closeIndex];
5366
5374
  const isActivePage = pageToClose === ctx.page;
@@ -5383,10 +5391,10 @@ async function handleClose(p, pages, ctx) {
5383
5391
  }
5384
5392
  async function handleSwitch(p, pages, ctx) {
5385
5393
  if (p.index === void 0) {
5386
- return fail6("Parameter --index is required for switch subcommand");
5394
+ return fail7("Parameter --index is required for switch subcommand");
5387
5395
  }
5388
5396
  if (p.index < 0 || p.index >= pages.length) {
5389
- return fail6(`Invalid tab index: ${p.index}. Valid range: 0-${pages.length - 1}`);
5397
+ return fail7(`Invalid tab index: ${p.index}. Valid range: 0-${pages.length - 1}`);
5390
5398
  }
5391
5399
  const targetPage = pages[p.index];
5392
5400
  await targetPage.bringToFront().catch(() => {
@@ -5638,7 +5646,7 @@ registerCommandDefinition("addinitscript", ["script"]);
5638
5646
 
5639
5647
  // src/commands/find.ts
5640
5648
  import { z as z25 } from "zod";
5641
- import { ok as ok24, fail as fail7, normalizeTips as normalizeTips5 } from "@dyyz1993/xcli-core";
5649
+ import { ok as ok24, fail as fail8, normalizeTips as normalizeTips5 } from "@dyyz1993/xcli-core";
5642
5650
  var actionSchema2 = z25.enum(["click", "fill", "type", "select", "hover", "check"]);
5643
5651
  var findCommand = registerCommand({
5644
5652
  name: "find",
@@ -5679,7 +5687,7 @@ var findCommand = registerCommand({
5679
5687
  });
5680
5688
  const count = await locator.count();
5681
5689
  if (count === 0) {
5682
- return fail7(`No element found with ${p.strategy}="${p.value}"`);
5690
+ return fail8(`No element found with ${p.strategy}="${p.value}"`);
5683
5691
  }
5684
5692
  const tips = [];
5685
5693
  const target = selectTarget(locator, p.strategy);
@@ -5691,15 +5699,15 @@ var findCommand = registerCommand({
5691
5699
  await target.click({ timeout: p.timeout, force: true });
5692
5700
  return okWithTips({ matched: count, selector, action: "click" }, tips);
5693
5701
  } else if (actionName === "fill") {
5694
- if (actionValue === void 0) return fail7("find fill requires a value");
5702
+ if (actionValue === void 0) return fail8("find fill requires a value");
5695
5703
  await target.fill(actionValue, { timeout: p.timeout, force: true });
5696
5704
  return okWithTips({ matched: count, selector, action: `fill("${actionValue}")` }, tips);
5697
5705
  } else if (actionName === "type") {
5698
- if (actionValue === void 0) return fail7("find type requires a value");
5706
+ if (actionValue === void 0) return fail8("find type requires a value");
5699
5707
  await target.type(actionValue, { delay: 10, timeout: p.timeout });
5700
5708
  return okWithTips({ matched: count, selector, action: `type("${actionValue}")` }, tips);
5701
5709
  } else if (actionName === "select") {
5702
- if (actionValue === void 0) return fail7("find select requires a value");
5710
+ if (actionValue === void 0) return fail8("find select requires a value");
5703
5711
  await target.selectOption(actionValue);
5704
5712
  return okWithTips({ matched: count, selector, action: `select("${actionValue}")` }, tips);
5705
5713
  } else if (actionName === "hover") {
@@ -7135,7 +7143,7 @@ async function guardCheck(commandName) {
7135
7143
  }
7136
7144
  }
7137
7145
  function errorResult(message) {
7138
- return { ...fail8(message), duration: 0 };
7146
+ return { ...fail9(message), duration: 0 };
7139
7147
  }
7140
7148
  function tipsToMessages(tips) {
7141
7149
  if (!tips || tips.length === 0) return [];
@@ -7205,9 +7213,14 @@ async function executeCommand(commandName, params, sessionName = "default", extr
7205
7213
  const { _target: _u, ...rest } = params;
7206
7214
  params = rest;
7207
7215
  }
7216
+ const _tabIndex = params._tabIndex;
7217
+ if (_tabIndex !== void 0) {
7218
+ const { _tabIndex: _u, ...rest } = params;
7219
+ params = rest;
7220
+ }
7208
7221
  let targetPageOverride = null;
7209
7222
  if (_target && extraOpts?.cdpEndpoint) {
7210
- const { findTargetPage } = await import("./browser-PUAJ3AHG.js");
7223
+ const { findTargetPage } = await import("./browser-XPKM344Y.js");
7211
7224
  targetPageOverride = await findTargetPage(extraOpts.cdpEndpoint, _target);
7212
7225
  if (!targetPageOverride) {
7213
7226
  return errorResult(`Target "${_target}" not found. Use 'xbrowser targets --cdp ${extraOpts.cdpEndpoint}' to list available pages.`);
@@ -7279,6 +7292,16 @@ async function executeCommand(commandName, params, sessionName = "default", extr
7279
7292
  cliName: "xbrowser",
7280
7293
  tips: new TipCollector2()
7281
7294
  };
7295
+ if (_tabIndex !== void 0 && session?.context) {
7296
+ const pages = session.context.pages();
7297
+ if (_tabIndex >= 0 && _tabIndex < pages.length) {
7298
+ const targetPage = pages[_tabIndex];
7299
+ await targetPage.bringToFront().catch(() => {
7300
+ });
7301
+ setActivePage(session, targetPage);
7302
+ ctx.page = targetPage;
7303
+ }
7304
+ }
7282
7305
  const start = Date.now();
7283
7306
  if (session) {
7284
7307
  streamCommandEvent(session.id, {
@@ -7307,7 +7330,17 @@ async function executeCommand(commandName, params, sessionName = "default", extr
7307
7330
  if (hooks.length > 0 && session?.page) {
7308
7331
  await Promise.all(hooks.map((h) => h.onBeforeCommand?.({ page: session.page, command: commandName, params })));
7309
7332
  }
7310
- const raw = await command.handler(params, ctx);
7333
+ let raw;
7334
+ const handlerPromise = command.handler(params, ctx);
7335
+ if (extraOpts?.timeout && extraOpts.timeout > 0) {
7336
+ const timeoutMs = extraOpts.timeout;
7337
+ const timeoutPromise = new Promise(
7338
+ (_, reject) => setTimeout(() => reject(new Error(`Command timed out after ${timeoutMs}ms`)), timeoutMs)
7339
+ );
7340
+ raw = await Promise.race([handlerPromise, timeoutPromise]);
7341
+ } else {
7342
+ raw = await handlerPromise;
7343
+ }
7311
7344
  const end = Date.now();
7312
7345
  const duration = end - start;
7313
7346
  let hookOutputs;
@@ -7411,7 +7444,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
7411
7444
  duration,
7412
7445
  timestamp: start
7413
7446
  });
7414
- return { ...fail8(errorMessage), duration };
7447
+ return { ...fail9(errorMessage), duration };
7415
7448
  } finally {
7416
7449
  }
7417
7450
  }
@@ -7452,7 +7485,7 @@ async function executeChain(input, options) {
7452
7485
  results.push({
7453
7486
  command: cmdName,
7454
7487
  raw: cmdStr,
7455
- ...fail8(`Plugin "${cmdName}" requires a sub-command`),
7488
+ ...fail9(`Plugin "${cmdName}" requires a sub-command`),
7456
7489
  duration: 0
7457
7490
  });
7458
7491
  if (type === "and") {
@@ -7471,7 +7504,7 @@ async function executeChain(input, options) {
7471
7504
  results.push({
7472
7505
  command: cmdName,
7473
7506
  raw: cmdStr,
7474
- ...fail8(`Unknown command "${subCommand}" for plugin "${cmdName}"`),
7507
+ ...fail9(`Unknown command "${subCommand}" for plugin "${cmdName}"`),
7475
7508
  duration: 0
7476
7509
  });
7477
7510
  if (type === "and") {
@@ -7603,7 +7636,7 @@ async function executeChain(input, options) {
7603
7636
  results.push({
7604
7637
  command: `${cmdName} ${subCommand}`,
7605
7638
  raw: cmdStr,
7606
- ...fail8(errorMessage),
7639
+ ...fail9(errorMessage),
7607
7640
  duration: duration2
7608
7641
  });
7609
7642
  if (type === "and") {
@@ -7883,16 +7916,29 @@ var configBuiltin = {
7883
7916
  execute: async (args, _options, _ctx) => {
7884
7917
  const [subcommand, ...rest] = args;
7885
7918
  if (!subcommand || subcommand === "list") {
7919
+ let flatten2 = function(obj, prefix = "") {
7920
+ const entries2 = [];
7921
+ for (const [k, v] of Object.entries(obj)) {
7922
+ const fullKey = prefix ? `${prefix}.${k}` : k;
7923
+ if (v && typeof v === "object" && !Array.isArray(v)) {
7924
+ entries2.push(...flatten2(v, fullKey));
7925
+ } else {
7926
+ entries2.push({ key: fullKey, value: v });
7927
+ }
7928
+ }
7929
+ return entries2;
7930
+ };
7931
+ var flatten = flatten2;
7886
7932
  const config = loadConfig();
7887
- const keys = Object.keys(config);
7888
- if (keys.length === 0) {
7933
+ const entries = flatten2(config);
7934
+ if (entries.length === 0) {
7889
7935
  console.log("Configuration is empty");
7890
7936
  return;
7891
7937
  }
7892
7938
  console.log("Configuration:");
7893
7939
  console.log("");
7894
- for (const k of keys) {
7895
- console.log(` ${k} = ${config[k]}`);
7940
+ for (const { key, value } of entries) {
7941
+ console.log(` ${key} = ${value}`);
7896
7942
  }
7897
7943
  return;
7898
7944
  }
@@ -8691,6 +8737,39 @@ function outputError(message) {
8691
8737
  console.error(formatted);
8692
8738
  process.exit(1);
8693
8739
  }
8740
+ function outputEnvelope(result, meta, mode) {
8741
+ if (mode !== "json" && mode !== "yaml") {
8742
+ if (!result.success) {
8743
+ outputError(result.message || "Unknown error");
8744
+ return;
8745
+ }
8746
+ outputResult(result.data, mode);
8747
+ return;
8748
+ }
8749
+ const { command, ...extraMeta } = meta;
8750
+ const commandResult = {
8751
+ success: result.success,
8752
+ data: result.data,
8753
+ message: result.message,
8754
+ tips: [],
8755
+ meta: {
8756
+ duration: result.duration ?? 0,
8757
+ ...extraMeta
8758
+ }
8759
+ };
8760
+ const formatted = outputFormatter.formatEnvelope(commandResult, {
8761
+ command,
8762
+ extraMeta,
8763
+ mode
8764
+ });
8765
+ console.log(formatted);
8766
+ if (result.tips?.length) {
8767
+ for (const tip of result.tips) {
8768
+ const text = typeof tip === "string" ? tip : tip.message;
8769
+ if (text) console.error(` \u{1F4A1} ${text}`);
8770
+ }
8771
+ }
8772
+ }
8694
8773
 
8695
8774
  // src/builtins/plugin.ts
8696
8775
  var pluginLoader2 = null;
@@ -10250,9 +10329,13 @@ async function handleBrowserCommand(command, args, options, sessionName, mode, c
10250
10329
  if (target) {
10251
10330
  params = { ...params, _target: target };
10252
10331
  }
10332
+ const tabIndex = options.tab;
10333
+ if (tabIndex !== void 0) {
10334
+ params = { ...params, _tabIndex: Number(tabIndex) };
10335
+ }
10253
10336
  const result = cdpEndpoint ? await executeCommand(cmdName, params, sessionName, { cdpEndpoint }) : await executeCommand(cmdName, params, sessionName);
10254
10337
  if (mode === "json" || mode === "yaml") {
10255
- outputResult(result, mode);
10338
+ outputEnvelope(result, { command: cmdName }, mode);
10256
10339
  } else if (!result.success) {
10257
10340
  outputError(result.message || "Command failed");
10258
10341
  } else {
@@ -10312,7 +10395,7 @@ async function handleSession(args, options, mode, _cdpEndpoint) {
10312
10395
  }
10313
10396
  } catch {
10314
10397
  }
10315
- outputResult({ ok: true, closed: count, all: true }, mode);
10398
+ outputEnvelope({ success: true, data: { closed: count, all: true } }, { command: "session close" }, mode);
10316
10399
  } else {
10317
10400
  const name = options.session || options.name || process.env.XBROWSER_SESSION || "default";
10318
10401
  try {
@@ -10320,7 +10403,7 @@ async function handleSession(args, options, mode, _cdpEndpoint) {
10320
10403
  } catch {
10321
10404
  }
10322
10405
  await closeSession(name);
10323
- outputResult({ ok: true, name }, mode);
10406
+ outputEnvelope({ success: true, data: { name } }, { command: "session close" }, mode);
10324
10407
  }
10325
10408
  break;
10326
10409
  }
@@ -10328,10 +10411,10 @@ async function handleSession(args, options, mode, _cdpEndpoint) {
10328
10411
  case "ls": {
10329
10412
  try {
10330
10413
  const sessions2 = await forwardSessionList();
10331
- outputResult({ sessions: sessions2 }, mode);
10414
+ outputEnvelope({ success: true, data: { sessions: sessions2 } }, { command: "session list" }, mode);
10332
10415
  } catch {
10333
10416
  const sessions2 = await listSessions();
10334
- outputResult({ sessions: sessions2 }, mode);
10417
+ outputEnvelope({ success: true, data: { sessions: sessions2 } }, { command: "session list" }, mode);
10335
10418
  }
10336
10419
  break;
10337
10420
  }
@@ -10346,7 +10429,7 @@ async function handleSession(args, options, mode, _cdpEndpoint) {
10346
10429
  await stopDaemonProcess();
10347
10430
  } catch {
10348
10431
  }
10349
- outputResult({ ok: true, name, killed: true, daemon: "stopped" }, mode);
10432
+ outputEnvelope({ success: true, data: { name, killed: true, daemon: "stopped" } }, { command: "session kill" }, mode);
10350
10433
  break;
10351
10434
  }
10352
10435
  case "kill-all": {
@@ -10365,7 +10448,7 @@ async function handleSession(args, options, mode, _cdpEndpoint) {
10365
10448
  } catch {
10366
10449
  }
10367
10450
  const cleaned = cleanSessionFiles();
10368
- outputResult({ ok: true, sessionsCleaned: cleaned, daemon: "killed" }, mode);
10451
+ outputEnvelope({ success: true, data: { sessionsCleaned: cleaned, daemon: "killed" } }, { command: "session kill-all" }, mode);
10369
10452
  break;
10370
10453
  }
10371
10454
  default:
@@ -10485,7 +10568,7 @@ async function handleSearch(args, options, mode) {
10485
10568
  }
10486
10569
  }
10487
10570
  if (mode === "json") {
10488
- outputResult({ results, total: results.length }, mode);
10571
+ outputEnvelope({ success: true, data: { results, total: results.length } }, { command: "plugin search" }, mode);
10489
10572
  } else {
10490
10573
  if (results.length === 0) {
10491
10574
  console.log("No plugins found");
@@ -10514,7 +10597,7 @@ async function handlePluginInfo(args, options, mode) {
10514
10597
  if (pluginInfo) {
10515
10598
  const d = pluginInfo;
10516
10599
  if (mode === "json") {
10517
- outputResult({ source: "marketplace", ...d }, mode);
10600
+ outputEnvelope({ success: true, data: { source: "marketplace", ...d } }, { command: "plugin info" }, mode);
10518
10601
  return;
10519
10602
  }
10520
10603
  console.log(`\u540D\u79F0: ${d.name || ""}`);
@@ -10540,7 +10623,7 @@ async function handlePluginInfo(args, options, mode) {
10540
10623
  const pkg = latest && versions?.[latest];
10541
10624
  if (pkg) {
10542
10625
  if (mode === "json") {
10543
- outputResult({ source: "npm", name: pkg.name, version: latest, description: pkg.description }, mode);
10626
+ outputEnvelope({ success: true, data: { source: "npm", name: pkg.name, version: latest, description: pkg.description } }, { command: "plugin info" }, mode);
10544
10627
  return;
10545
10628
  }
10546
10629
  console.log(`\u540D\u79F0: ${pkg.name || ""}`);
@@ -10569,7 +10652,7 @@ async function handlePluginSchema(args, mode) {
10569
10652
  return;
10570
10653
  }
10571
10654
  if (mode === "json") {
10572
- outputResult(contract, mode);
10655
+ outputEnvelope({ success: true, data: contract }, { command: "plugin schema" }, mode);
10573
10656
  return;
10574
10657
  }
10575
10658
  if ("commands" in contract) {
@@ -10647,8 +10730,9 @@ async function handlePlugin(args, options, mode) {
10647
10730
  }
10648
10731
  } catch {
10649
10732
  }
10650
- outputResult(
10651
- { ok: true, name: result.name, source: result.source, path: result.path },
10733
+ outputEnvelope(
10734
+ { success: true, data: { name: result.name, source: result.source, path: result.path } },
10735
+ { command: "plugin install" },
10652
10736
  mode
10653
10737
  );
10654
10738
  break;
@@ -10667,7 +10751,7 @@ async function handlePlugin(args, options, mode) {
10667
10751
  await loader.reloadPlugin(name);
10668
10752
  } catch {
10669
10753
  }
10670
- outputResult({ ok: true, name }, mode);
10754
+ outputEnvelope({ success: true, data: { name } }, { command: "plugin uninstall" }, mode);
10671
10755
  break;
10672
10756
  }
10673
10757
  case "list": {
@@ -10689,7 +10773,7 @@ async function handlePlugin(args, options, mode) {
10689
10773
  };
10690
10774
  });
10691
10775
  if (mode === "json") {
10692
- outputResult({ plugins: enrichedPlugins }, mode);
10776
+ outputEnvelope({ success: true, data: { plugins: enrichedPlugins } }, { command: "plugin list" }, mode);
10693
10777
  } else {
10694
10778
  if (enrichedPlugins.length === 0) {
10695
10779
  console.log("No plugins installed");
@@ -10725,7 +10809,7 @@ Total: ${enrichedPlugins.length} plugins`);
10725
10809
  } catch {
10726
10810
  outputError(`Plugin "${name}" not found. Use 'xbrowser plugin list' to see installed plugins.`);
10727
10811
  }
10728
- outputResult({ ok: true, name }, mode);
10812
+ outputEnvelope({ success: true, data: { name } }, { command: "plugin reload" }, mode);
10729
10813
  break;
10730
10814
  }
10731
10815
  case "search":
@@ -10757,24 +10841,22 @@ function handleDaemon(args, options, mode) {
10757
10841
  case "start": {
10758
10842
  const port = options.port ? Number(options.port) : 9224;
10759
10843
  startDaemonProcess(port).then(
10760
- (config) => outputResult({ ok: true, pid: config.pid, port: config.port }, mode)
10844
+ (config) => outputEnvelope({ success: true, data: { pid: config.pid, port: config.port } }, { command: "daemon start" }, mode)
10761
10845
  ).catch(
10762
10846
  (e) => outputError(e instanceof Error ? e.message : String(e))
10763
10847
  );
10764
10848
  break;
10765
10849
  }
10766
10850
  case "stop": {
10767
- stopDaemonProcess().then(() => outputResult({ ok: true }, mode)).catch(
10851
+ stopDaemonProcess().then(() => outputEnvelope({ success: true, data: {} }, { command: "daemon stop" }, mode)).catch(
10768
10852
  (e) => outputError(e instanceof Error ? e.message : String(e))
10769
10853
  );
10770
10854
  break;
10771
10855
  }
10772
10856
  case "status": {
10773
10857
  const status = getDaemonProcessStatus();
10774
- outputResult(
10775
- status.running ? { running: true, pid: status.pid, port: status.port } : { running: false },
10776
- mode
10777
- );
10858
+ const statusData = status.running ? { running: true, pid: status.pid, port: status.port } : { running: false };
10859
+ outputEnvelope({ success: true, data: statusData }, { command: "daemon status" }, mode);
10778
10860
  break;
10779
10861
  }
10780
10862
  default:
@@ -11152,7 +11234,7 @@ async function handleFilter(args, _mode, options) {
11152
11234
  console.log(` Original: ${result.originalCount}, After: ${result.filteredCount}, Removed: ${result.removed} (${result.percentage}%)`);
11153
11235
  }
11154
11236
  async function handleGeneratePlugin(sessionName, pluginName, outputDir) {
11155
- const { SessionRecorder: SessionRecorder2 } = await import("./session-recorder-H2WPBCSW.js");
11237
+ const { SessionRecorder: SessionRecorder2 } = await import("./session-recorder-33UKWCIR.js");
11156
11238
  const { readSiteKnowledge: readSiteKnowledge2, toMarkdown } = await import("./site-knowledge-SYC6VCDB.js");
11157
11239
  const { mkdirSync: mkdirSync10, writeFileSync: writeFileSync12 } = await import("fs");
11158
11240
  const { join: join14 } = await import("path");
@@ -12499,7 +12581,8 @@ var KNOWN_GLOBAL_OPTIONS = /* @__PURE__ */ new Set([
12499
12581
  "port",
12500
12582
  "token",
12501
12583
  "timeout",
12502
- "headless"
12584
+ "headless",
12585
+ "tab"
12503
12586
  ]);
12504
12587
  function showCommandHelp(siteName, cmd, siteConfig, mode) {
12505
12588
  const c = cmd;
@@ -12653,20 +12736,28 @@ async function handleEvalMode(argv) {
12653
12736
  }
12654
12737
  async function handleChainInput(input, argv) {
12655
12738
  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;
12739
+ const hasJson = argv ? argv.some((a) => a === "--json" || a.startsWith("--json=") || a.includes(" --json") || a.startsWith("--json")) || argv.includes("-j") : false;
12740
+ const hasYaml = argv ? argv.some((a) => a === "--yaml" || a.startsWith("--yaml=")) : false;
12741
+ const mode = hasJson ? "json" : hasYaml ? "yaml" : "text";
12657
12742
  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));
12743
+ if (mode === "json" || mode === "yaml") {
12744
+ outputEnvelope(
12745
+ {
12746
+ success: chainResult.success,
12747
+ data: {
12748
+ steps: chainResult.steps.map((s) => ({
12749
+ command: s.raw,
12750
+ success: s.success,
12751
+ data: s.data,
12752
+ duration: s.duration,
12753
+ ...s.hookOutputs?.length ? { hooks: s.hookOutputs } : {}
12754
+ }))
12755
+ },
12756
+ duration: chainResult.totalDuration
12757
+ },
12758
+ { command: "chain", totalSteps: chainResult.steps.length },
12759
+ mode
12760
+ );
12670
12761
  } else {
12671
12762
  printChainResult(chainResult);
12672
12763
  }
@@ -12733,7 +12824,15 @@ async function routeCommand(argvIn, stdinCommands) {
12733
12824
  const sessionName = options.session || process.env.XBROWSER_SESSION || "default";
12734
12825
  const cdpEndpoint = options.cdp || process.env.XBROWSER_CDP;
12735
12826
  if (options.version || options.v && positional.length === 0) {
12736
- console.log(`xbrowser v${version}`);
12827
+ if (mode === "json") {
12828
+ outputEnvelope(
12829
+ { success: true, data: { version, name: "@xbrowser/cli" } },
12830
+ { command: "version" },
12831
+ mode
12832
+ );
12833
+ } else {
12834
+ console.log(`xbrowser v${version}`);
12835
+ }
12737
12836
  return;
12738
12837
  }
12739
12838
  if (positional.length === 0) {
@@ -12929,18 +13028,23 @@ async function routeCommand(argvIn, stdinCommands) {
12929
13028
  if (isChainInput(fullInput)) {
12930
13029
  const chainResult = await executeChain(fullInput, { cdpEndpoint, sessionName });
12931
13030
  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);
13031
+ outputEnvelope(
13032
+ {
13033
+ success: chainResult.success,
13034
+ data: {
13035
+ steps: chainResult.steps.map((s) => ({
13036
+ command: s.raw,
13037
+ success: s.success,
13038
+ data: s.data,
13039
+ duration: s.duration
13040
+ })),
13041
+ ...chainResult.stoppedReason ? { stoppedReason: chainResult.stoppedReason } : {}
13042
+ },
13043
+ duration: chainResult.totalDuration
13044
+ },
13045
+ { command: "chain", totalSteps: chainResult.steps.length },
13046
+ mode
13047
+ );
12944
13048
  if (!chainResult.success) throw new Error("Command failed");
12945
13049
  return;
12946
13050
  }
@@ -13085,6 +13189,17 @@ Run "xbrowser ${command} ${subCommand} --help" to see available parameters.`
13085
13189
  session = await createSession(sessionName, void 0, cdpEndpoint ? { cdpEndpoint } : {});
13086
13190
  }
13087
13191
  }
13192
+ const cmdTabIndex = options.tab !== void 0 ? Number(options.tab) : void 0;
13193
+ if (cmdTabIndex !== void 0 && session?.context) {
13194
+ const pages = session.context.pages();
13195
+ if (cmdTabIndex >= 0 && cmdTabIndex < pages.length) {
13196
+ const targetPage = pages[cmdTabIndex];
13197
+ await targetPage.bringToFront().catch(() => {
13198
+ });
13199
+ const { setActivePage: setActivePage2 } = await import("./browser-XPKM344Y.js");
13200
+ setActivePage2(session, targetPage);
13201
+ }
13202
+ }
13088
13203
  const ctx = {
13089
13204
  args: cmdArgsForPlugin,
13090
13205
  options,
@@ -13162,22 +13277,17 @@ Run "xbrowser ${command} ${subCommand} --help" to see available parameters.`
13162
13277
  const outputData = isCommandResult2(result) ? result.data : result && typeof result === "object" ? result.data ?? result : result;
13163
13278
  const tips = isCommandResult2(result) ? result.tips : result && typeof result === "object" ? result.tips : void 0;
13164
13279
  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
- }
13280
+ const resultSuccess = isCommandResult2(result) ? result.success !== false : true;
13281
+ const resultMsg = isCommandResult2(result) ? result.message : void 0;
13282
+ const duration = Date.now() - cmdStart;
13283
+ const envelopeMeta = { command: `${command} ${subCommand}` };
13284
+ if (injectedViewerUrl) envelopeMeta.viewerUrl = injectedViewerUrl;
13285
+ if (hookOutputs.length > 0) envelopeMeta.hooks = hookOutputs;
13286
+ outputEnvelope(
13287
+ { success: resultSuccess, data: outputData, message: resultMsg, tips, duration },
13288
+ envelopeMeta,
13289
+ mode
13290
+ );
13181
13291
  } else {
13182
13292
  console.log(outputFormatter2.format(outputData, { mode: "text", color: true, emoji: true }));
13183
13293
  if (tips?.length) {
@@ -13359,7 +13469,7 @@ async function main() {
13359
13469
  const command = process.argv[2];
13360
13470
  const isLongRunning = command === "preview" || command === "serve";
13361
13471
  if (!isLongRunning) {
13362
- const { ensureProcessCanExit } = await import("./browser-PUAJ3AHG.js");
13472
+ const { ensureProcessCanExit } = await import("./browser-XPKM344Y.js");
13363
13473
  await ensureProcessCanExit().catch(() => {
13364
13474
  });
13365
13475
  process.exit(exitCode);