@xbrowser/cli 1.21.0 → 1.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/README.md +21 -3
  2. package/dist/{browser-CNBSH53B.js → browser-BVRFSUXU.js} +3 -2
  3. package/dist/{browser-C2VEOJC2.js → browser-DSYUAE7H.js} +5 -4
  4. package/dist/{browser-VZOOFLAH.js → browser-Q67BJTI2.js} +4 -3
  5. package/dist/{cdp-driver-KFX4A6WR.js → cdp-driver-DC7EIUG5.js} +4 -3
  6. package/dist/{cdp-driver-7Z6ZAT5E.js → cdp-driver-T7D6H2RW.js} +335 -546
  7. package/dist/{cdp-driver-26LGCHXV.js → cdp-driver-WWO2UZDR.js} +3 -2
  8. package/dist/{chunk-Z5RTK4GW.js → chunk-2Q3GQRUP.js} +2 -2
  9. package/dist/{chunk-CLYZPSDR.js → chunk-4OU37CXP.js} +354 -576
  10. package/dist/{chunk-HWWG2ISJ.js → chunk-7OP2ISEY.js} +353 -575
  11. package/dist/{chunk-PIWNMC36.js → chunk-A3MORKYR.js} +1 -1
  12. package/dist/{chunk-3FWLW7FS.js → chunk-A7NEA4PA.js} +39 -1
  13. package/dist/{chunk-OMU63E6J.js → chunk-FBQZ3PVD.js} +5 -8
  14. package/dist/chunk-GYB5BOSP.js +602 -0
  15. package/dist/{chunk-ESJYANQO.js → chunk-IFKJO2UR.js} +360 -582
  16. package/dist/{chunk-NODRQGOK.js → chunk-IR6C24P7.js} +97 -0
  17. package/dist/{chunk-TEXCXIBW.js → chunk-LPW6QS4M.js} +5 -8
  18. package/dist/{chunk-3KZ34AUC.js → chunk-MYH6FIBZ.js} +2 -1
  19. package/dist/{chunk-AVSTNODR.js → chunk-T4WTKBWI.js} +2 -2
  20. package/dist/{chunk-HBHOKZPN.js → chunk-V6KGBU5J.js} +97 -0
  21. package/dist/{chunk-IWVG4XYZ.js → chunk-VCVDILH6.js} +2 -1
  22. package/dist/{chunk-NKW4A74J.js → chunk-XTUAUFL3.js} +5 -8
  23. package/dist/cli.js +497 -252
  24. package/dist/daemon-main.js +25 -19
  25. package/dist/{human-interaction-Y5IDH6LD.js → human-interaction-57VYIR56.js} +1 -1
  26. package/dist/{human-interaction-ZUTR5AC2.js → human-interaction-67K43B4O.js} +1 -1
  27. package/dist/{human-interaction-4YR2N6R6.js → human-interaction-MXST5ZQM.js} +1 -1
  28. package/dist/index.d.ts +52 -4
  29. package/dist/index.js +500 -256
  30. package/dist/keep-awake-M4KJS4B4.js +29 -0
  31. package/dist/{launcher-44STYRJC.js → launcher-IK2FKW6T.js} +1 -1
  32. package/dist/{launcher-EXJHHAUL.js → launcher-LUOMMGZC.js} +1 -1
  33. package/dist/{plugin-singleton-FCNDN3FX.js → plugin-singleton-GJO7EZXB.js} +1 -1
  34. package/dist/{recovery-NXC35EQN.js → recovery-6RI2FE3E.js} +1 -1
  35. package/dist/{recovery-MDWAVXE4.js → recovery-LGY25LIM.js} +1 -1
  36. package/dist/{recovery-L5GLDX4O.js → recovery-LMJW537X.js} +1 -1
  37. package/dist/{session-recorder-SLDBENVF.js → session-recorder-563ISSJK.js} +1 -1
  38. package/dist/{session-recorder-3BEVWHOK.js → session-recorder-GEZVQBKW.js} +1 -1
  39. package/dist/session-replayer-N665T3G5.js +932 -0
  40. package/dist/stealth-2AA2FSNN.js +29 -0
  41. package/dist/stealth-JOXGV34D.js +29 -0
  42. package/package.json +11 -7
  43. package/dist/session-replayer-EVD4HZBB.js +0 -413
package/dist/index.js CHANGED
@@ -1,7 +1,15 @@
1
+ import {
2
+ extractAndSave,
3
+ extractRecording,
4
+ printExtractSummary
5
+ } from "./chunk-X46HDAOT.js";
1
6
  import {
2
7
  filterRecording,
3
8
  parseExcludeTypes
4
9
  } from "./chunk-ANVL2ID2.js";
10
+ import {
11
+ SessionRecorder
12
+ } from "./chunk-IR6C24P7.js";
5
13
  import {
6
14
  closeAllSessions,
7
15
  closeEphemeralContext,
@@ -18,7 +26,7 @@ import {
18
26
  resolveLaunchOpts,
19
27
  saveSessionDiskMeta,
20
28
  setActivePage
21
- } from "./chunk-Z5RTK4GW.js";
29
+ } from "./chunk-2Q3GQRUP.js";
22
30
  import {
23
31
  detectAntiBot,
24
32
  formatDetectionMessage
@@ -64,7 +72,7 @@ import {
64
72
  resolveNpmPackageWithFallback,
65
73
  resolveScreenshotsDir,
66
74
  setConfigValue
67
- } from "./chunk-NKW4A74J.js";
75
+ } from "./chunk-XTUAUFL3.js";
68
76
  import {
69
77
  getDaemonConfig,
70
78
  getDaemonProcessStatus,
@@ -77,17 +85,13 @@ import {
77
85
  generateJSScript,
78
86
  generatePythonScript
79
87
  } from "./chunk-ETCO4SNK.js";
80
- import {
81
- extractAndSave,
82
- extractRecording,
83
- printExtractSummary
84
- } from "./chunk-X46HDAOT.js";
88
+ import "./chunk-7OP2ISEY.js";
85
89
  import {
86
90
  rand,
87
91
  sleep,
88
92
  wheelDelta
89
- } from "./chunk-HWWG2ISJ.js";
90
- import "./chunk-3KZ34AUC.js";
93
+ } from "./chunk-GYB5BOSP.js";
94
+ import "./chunk-MYH6FIBZ.js";
91
95
  import {
92
96
  errMsg
93
97
  } from "./chunk-GDKLH7ZY.js";
@@ -107,9 +111,6 @@ import {
107
111
  networkAnomalyRule,
108
112
  pageLifecycleRule
109
113
  } from "./chunk-75DNUI6M.js";
110
- import {
111
- SessionRecorder
112
- } from "./chunk-NODRQGOK.js";
113
114
  import {
114
115
  addKnownIssue,
115
116
  getKnowledgePath,
@@ -7402,7 +7403,7 @@ function attachWaitForHuman(ctx, getOrCreateWSServer) {
7402
7403
  if (!ctx.page) {
7403
7404
  throw new Error("waitForHuman requires an active page");
7404
7405
  }
7405
- const { HumanInteractionManager: HumanInteractionManager2 } = await import("./human-interaction-Y5IDH6LD.js");
7406
+ const { HumanInteractionManager: HumanInteractionManager2 } = await import("./human-interaction-57VYIR56.js");
7406
7407
  const wsServer2 = await getOrCreateWSServer(ctx.browserContext);
7407
7408
  const manager = new HumanInteractionManager2(wsServer2, ctx.page);
7408
7409
  return manager.waitForHuman(options);
@@ -7518,7 +7519,7 @@ import { join as join7 } from "path";
7518
7519
  import { execSync } from "child_process";
7519
7520
  var SHARED_PLUGIN_DEPENDENCIES = {
7520
7521
  "zod": "^3.24.0",
7521
- "@dyyz1993/xcli-core": "^0.12.1"
7522
+ "@dyyz1993/xcli-core": "^0.19.0"
7522
7523
  };
7523
7524
  function ensurePluginDependencies(pluginsDir) {
7524
7525
  const zodPath = join7(pluginsDir, "node_modules", "zod");
@@ -8599,7 +8600,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
8599
8600
  }
8600
8601
  let targetPageOverride = null;
8601
8602
  if (_target && extraOpts?.cdpEndpoint) {
8602
- const { findTargetPage } = await import("./browser-VZOOFLAH.js");
8603
+ const { findTargetPage } = await import("./browser-Q67BJTI2.js");
8603
8604
  targetPageOverride = await findTargetPage(extraOpts.cdpEndpoint, _target);
8604
8605
  if (!targetPageOverride) {
8605
8606
  return errorResult(`Target "${_target}" not found. Use 'xbrowser targets --cdp ${extraOpts.cdpEndpoint}' to list available pages.`);
@@ -8816,7 +8817,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
8816
8817
  const errorMessage = errMsg(err);
8817
8818
  if (session?.page && process.env.XBROWSER_RECOVERY && !extraOpts?._recoveryAttempted) {
8818
8819
  try {
8819
- const { attemptRecovery } = await import("./recovery-L5GLDX4O.js");
8820
+ const { attemptRecovery } = await import("./recovery-LMJW537X.js");
8820
8821
  const recovery = await attemptRecovery(
8821
8822
  session.page,
8822
8823
  sessionName,
@@ -9531,13 +9532,39 @@ async function installFromNpm(packageName, name, targetDir) {
9531
9532
  import { existsSync as existsSync9, readFileSync as readFileSync7, writeFileSync as writeFileSync7, rmSync as rmSync3, cpSync as cpSync3 } from "fs";
9532
9533
  import { resolve as resolve5, join as join10 } from "path";
9533
9534
  import { tmpdir as tmpdir2 } from "os";
9534
- import { execSync as execSync2 } from "child_process";
9535
+ import { execFile as execFile2 } from "child_process";
9535
9536
  import { verifyPlugin as verifyPlugin3, safeCleanup as safeCleanup3 } from "@dyyz1993/xcli-core";
9537
+ var GIT_URL_RE = /^(?:https|git|git\+https|git\+ssh|ssh):\/\/[^\s"'`\\;<>()&]+$/i;
9538
+ var GIT_SCP_RE = /^git@[\w.-]+:[\w./~+-]+$/;
9539
+ function isValidGitUrl(url) {
9540
+ if (typeof url !== "string" || url.length === 0) return false;
9541
+ if (url.startsWith("-")) return false;
9542
+ if (/[\r\n\0]/.test(url)) return false;
9543
+ return GIT_URL_RE.test(url) || GIT_SCP_RE.test(url);
9544
+ }
9545
+ function execGit(args) {
9546
+ return new Promise((resolve10, reject) => {
9547
+ execFile2("git", args, (err, _stdout, stderr) => {
9548
+ if (err) {
9549
+ const stderrText = typeof stderr === "string" ? stderr : stderr?.toString("utf-8") ?? "";
9550
+ const detail = stderrText.trim() || err.message;
9551
+ reject(new Error(`git clone failed: ${detail}`));
9552
+ } else {
9553
+ resolve10();
9554
+ }
9555
+ });
9556
+ });
9557
+ }
9536
9558
  async function installFromGit(gitUrl, name, targetDir) {
9559
+ if (!isValidGitUrl(gitUrl)) {
9560
+ throw new Error(
9561
+ `Invalid git URL: ${JSON.stringify(gitUrl).slice(0, 120)} \u2014 allowed forms: https://, git://, git+https://, git+ssh://, ssh://, git@host:path`
9562
+ );
9563
+ }
9537
9564
  const tmpDir = join10(tmpdir2(), `xbrowser-git-${Date.now()}`);
9538
9565
  let warnings = [];
9539
9566
  try {
9540
- execSync2(`git clone --depth 1 "${gitUrl}" "${tmpDir}"`, { stdio: "pipe" });
9567
+ await execGit(["clone", "--depth", "1", "--", gitUrl, tmpDir]);
9541
9568
  const verify = verifyPlugin3(tmpDir, { metadataField: "xbrowser" });
9542
9569
  warnings = verify.warnings ?? [];
9543
9570
  if (!verify.valid) {
@@ -11377,7 +11404,7 @@ async function handleBrowserCommand(command, args, options, sessionName, mode, c
11377
11404
  }
11378
11405
  outputResult({ command: cmdDef.name, description: cmdDef.description, scope: cmdDef.scope, parameters: paramsList }, mode);
11379
11406
  } else {
11380
- console.log(helpGenerator.generate(cmdDef, { color: true, emoji: false }));
11407
+ process.stdout.write(helpGenerator.generate(cmdDef, { color: true, emoji: false }));
11381
11408
  }
11382
11409
  } else {
11383
11410
  outputError(`Unknown command: ${command}`);
@@ -11778,7 +11805,7 @@ async function handleBrowserCommand(command, args, options, sessionName, mode, c
11778
11805
  if (isEmptyResult) {
11779
11806
  const hint = cdpEndpoint ? `\u53EF\u80FD\u672A\u8FDE\u63A5\u5230\u6D4F\u89C8\u5668\u3002\u8BF7\u786E\u8BA4 ${cdpEndpoint} \u4E0A\u6709 Chrome \u8FD0\u884C\uFF08--remote-debugging-port\uFF09\u3002` : "\u53EF\u80FD\u672A\u8FDE\u63A5\u5230\u6D4F\u89C8\u5668\u3002\u8BF7\u4F7F\u7528 --cdp <endpoint> \u8FDE\u63A5\uFF0C\u6216\u5B89\u88C5 cdp-tunnel \u590D\u7528\u5DF2\u6709 Chrome\u3002";
11780
11807
  outputResult(result.data, mode);
11781
- console.error(`
11808
+ outputError(`
11782
11809
  \u26A0\uFE0F ${hint}`);
11783
11810
  process.exit(1);
11784
11811
  } else {
@@ -11795,8 +11822,9 @@ async function handleBrowserCommand(command, args, options, sessionName, mode, c
11795
11822
  try {
11796
11823
  mkdirSync11(dirname7(outputFile), { recursive: true });
11797
11824
  writeFileSync13(outputFile, content, "utf-8");
11798
- console.log(`
11799
- \u{1F4C4} Written to ${outputFile}`);
11825
+ process.stdout.write(`
11826
+ \u{1F4C4} Written to ${outputFile}
11827
+ `);
11800
11828
  } catch (err) {
11801
11829
  outputError(`Failed to write --output "${outputFile}": ${err instanceof Error ? err.message : String(err)}`);
11802
11830
  }
@@ -11894,7 +11922,7 @@ async function handleSession(args, options, mode, _cdpEndpoint) {
11894
11922
  break;
11895
11923
  }
11896
11924
  default:
11897
- console.log(handleSessionHelp());
11925
+ process.stdout.write(handleSessionHelp() + "\n");
11898
11926
  }
11899
11927
  }
11900
11928
 
@@ -12068,19 +12096,24 @@ async function handleSearch(args, options, mode) {
12068
12096
  outputEnvelope({ success: true, data: { results: deduped, total: deduped.length } }, { command: "plugin search" }, mode);
12069
12097
  } else {
12070
12098
  if (deduped.length === 0) {
12071
- console.log("No plugins found");
12099
+ process.stdout.write("No plugins found\n");
12072
12100
  return;
12073
12101
  }
12074
12102
  for (const r of deduped) {
12075
12103
  const src = r.source === "marketplace" ? "[marketplace]" : r.source === "local" ? "[local]" : "[npm]";
12076
12104
  const slug = r.slug ? ` (${r.slug})` : "";
12077
- console.log(` ${src} ${r.name}${slug}`);
12078
- if (r.description) console.log(` ${r.description}`);
12079
- if (r.version) console.log(` Version: ${r.version}`);
12080
- if (r.downloads) console.log(` Downloads: ${r.downloads}`);
12081
- console.log("");
12105
+ process.stdout.write(` ${src} ${r.name}${slug}
12106
+ `);
12107
+ if (r.description) process.stdout.write(` ${r.description}
12108
+ `);
12109
+ if (r.version) process.stdout.write(` Version: ${r.version}
12110
+ `);
12111
+ if (r.downloads) process.stdout.write(` Downloads: ${r.downloads}
12112
+ `);
12113
+ process.stdout.write("\n");
12082
12114
  }
12083
- console.log(`Total: ${deduped.length} plugins`);
12115
+ process.stdout.write(`Total: ${deduped.length} plugins
12116
+ `);
12084
12117
  }
12085
12118
  }
12086
12119
  async function handlePluginInfo(args, options, mode) {
@@ -12097,14 +12130,22 @@ async function handlePluginInfo(args, options, mode) {
12097
12130
  outputEnvelope({ success: true, data: { source: "marketplace", ...d } }, { command: "plugin info" }, mode);
12098
12131
  return;
12099
12132
  }
12100
- console.log(`\u540D\u79F0: ${d.name || ""}`);
12101
- console.log(`\u7248\u672C: ${d.version || ""}`);
12102
- console.log(`\u63CF\u8FF0: ${d.description || ""}`);
12103
- console.log(`\u4F5C\u8005: ${d.author || ""}`);
12104
- console.log(`\u547D\u4EE4: ${(d.commands || []).join(", ")}`);
12105
- console.log(`\u4E0B\u8F7D\u91CF: ${d.downloads || 0}`);
12106
- console.log(`\u6807\u7B7E: ${(d.tags || []).join(", ")}`);
12107
- console.log(`\u7F51\u7AD9: ${(d.sites || []).join(", ")}`);
12133
+ process.stdout.write(`\u540D\u79F0: ${d.name || ""}
12134
+ `);
12135
+ process.stdout.write(`\u7248\u672C: ${d.version || ""}
12136
+ `);
12137
+ process.stdout.write(`\u63CF\u8FF0: ${d.description || ""}
12138
+ `);
12139
+ process.stdout.write(`\u4F5C\u8005: ${d.author || ""}
12140
+ `);
12141
+ process.stdout.write(`\u547D\u4EE4: ${(d.commands || []).join(", ")}
12142
+ `);
12143
+ process.stdout.write(`\u4E0B\u8F7D\u91CF: ${d.downloads || 0}
12144
+ `);
12145
+ process.stdout.write(`\u6807\u7B7E: ${(d.tags || []).join(", ")}
12146
+ `);
12147
+ process.stdout.write(`\u7F51\u7AD9: ${(d.sites || []).join(", ")}
12148
+ `);
12108
12149
  return;
12109
12150
  }
12110
12151
  } catch {
@@ -12123,19 +12164,25 @@ async function handlePluginInfo(args, options, mode) {
12123
12164
  outputEnvelope({ success: true, data: { source: "npm", name: pkg.name, version: latest, description: pkg.description } }, { command: "plugin info" }, mode);
12124
12165
  return;
12125
12166
  }
12126
- console.log(`\u540D\u79F0: ${pkg.name || ""}`);
12127
- console.log(`\u7248\u672C: ${latest}`);
12128
- console.log(`\u63CF\u8FF0: ${pkg.description || ""}`);
12167
+ process.stdout.write(`\u540D\u79F0: ${pkg.name || ""}
12168
+ `);
12169
+ process.stdout.write(`\u7248\u672C: ${latest}
12170
+ `);
12171
+ process.stdout.write(`\u63CF\u8FF0: ${pkg.description || ""}
12172
+ `);
12129
12173
  const author = pkg.author;
12130
- console.log(`\u4F5C\u8005: ${typeof author === "string" ? author : author?.name || ""}`);
12131
- console.log(`\u5173\u952E\u8BCD: ${(pkg.keywords || []).join(", ")}`);
12132
- console.log(`\u8BB8\u53EF\u8BC1: ${pkg.license || ""}`);
12174
+ process.stdout.write(`\u4F5C\u8005: ${typeof author === "string" ? author : author?.name || ""}
12175
+ `);
12176
+ process.stdout.write(`\u5173\u952E\u8BCD: ${(pkg.keywords || []).join(", ")}
12177
+ `);
12178
+ process.stdout.write(`\u8BB8\u53EF\u8BC1: ${pkg.license || ""}
12179
+ `);
12133
12180
  return;
12134
12181
  }
12135
12182
  }
12136
- console.error(`\u63D2\u4EF6 '${slug}' \u672A\u627E\u5230`);
12183
+ outputError(`\u63D2\u4EF6 '${slug}' \u672A\u627E\u5230`);
12137
12184
  } catch (err) {
12138
- console.error("\u67E5\u8BE2\u5931\u8D25:", errMsg(err));
12185
+ outputError("\u67E5\u8BE2\u5931\u8D25: " + errMsg(err));
12139
12186
  }
12140
12187
  }
12141
12188
  async function handlePluginSchema(args, mode) {
@@ -12159,32 +12206,39 @@ async function handlePluginSchema(args, mode) {
12159
12206
  }
12160
12207
  }
12161
12208
  function printPluginContract(contract) {
12162
- console.log(`${contract.plugin.name} contract v${contract.version}`);
12163
- if (contract.plugin.description) console.log(contract.plugin.description);
12164
- console.log("");
12209
+ process.stdout.write(`${contract.plugin.name} contract v${contract.version}
12210
+ `);
12211
+ if (contract.plugin.description) process.stdout.write(contract.plugin.description + "\n");
12212
+ process.stdout.write("\n");
12165
12213
  for (const command of contract.commands) {
12166
12214
  printCommandContract(contract.plugin.name, command);
12167
12215
  }
12168
12216
  }
12169
12217
  function printCommandContract(pluginName, command) {
12170
- console.log(`${pluginName} ${command.name}`);
12171
- if (command.description) console.log(` ${command.description}`);
12172
- console.log(` scope: ${command.scope}`);
12218
+ process.stdout.write(`${pluginName} ${command.name}
12219
+ `);
12220
+ if (command.description) process.stdout.write(` ${command.description}
12221
+ `);
12222
+ process.stdout.write(` scope: ${command.scope}
12223
+ `);
12173
12224
  if (command.capabilities.length > 0) {
12174
- console.log(` capabilities: ${command.capabilities.join(", ")}`);
12225
+ process.stdout.write(` capabilities: ${command.capabilities.join(", ")}
12226
+ `);
12175
12227
  }
12176
12228
  if (command.positional.length > 0) {
12177
- console.log(` positional: ${command.positional.join(", ")}`);
12229
+ process.stdout.write(` positional: ${command.positional.join(", ")}
12230
+ `);
12178
12231
  }
12179
12232
  if (command.form.fields.length > 0) {
12180
- console.log(" fields:");
12233
+ process.stdout.write(" fields:\n");
12181
12234
  for (const field of command.form.fields) {
12182
12235
  const required = field.required ? "required" : "optional";
12183
12236
  const choices = field.enum ? ` [${field.enum.join("|")}]` : "";
12184
- console.log(` --${field.name}: ${field.type}/${field.widget} ${required}${choices}`);
12237
+ process.stdout.write(` --${field.name}: ${field.type}/${field.widget} ${required}${choices}
12238
+ `);
12185
12239
  }
12186
12240
  }
12187
- console.log("");
12241
+ process.stdout.write("\n");
12188
12242
  }
12189
12243
  async function handlePlugin(args, options, mode) {
12190
12244
  const sub = args[0];
@@ -12273,24 +12327,28 @@ async function handlePlugin(args, options, mode) {
12273
12327
  outputEnvelope({ success: true, data: { plugins: enrichedPlugins } }, { command: "plugin list" }, mode);
12274
12328
  } else {
12275
12329
  if (enrichedPlugins.length === 0) {
12276
- console.log("No plugins installed");
12330
+ process.stdout.write("No plugins installed\n");
12277
12331
  return;
12278
12332
  }
12279
12333
  for (const p of enrichedPlugins) {
12280
12334
  const loginTag = p.hasLogin ? p.loggedIn ? " [logged in]" : " [need login]" : "";
12281
12335
  if (p.version && p.description) {
12282
- console.log(`${p.name} (${p.version}) - ${p.description}${loginTag}`);
12336
+ process.stdout.write(`${p.name} (${p.version}) - ${p.description}${loginTag}
12337
+ `);
12283
12338
  } else {
12284
- console.log(`${p.name}${loginTag}`);
12339
+ process.stdout.write(`${p.name}${loginTag}
12340
+ `);
12285
12341
  }
12286
12342
  if (p.commands && p.commands.length > 0) {
12287
- console.log(` ${p.commands.join(", ")}`);
12343
+ process.stdout.write(` ${p.commands.join(", ")}
12344
+ `);
12288
12345
  }
12289
12346
  if (p.requiresLoginCommands.length > 0) {
12290
- console.log(` requires login: ${p.requiresLoginCommands.join(", ")}`);
12347
+ process.stdout.write(` requires login: ${p.requiresLoginCommands.join(", ")}
12348
+ `);
12291
12349
  }
12292
12350
  }
12293
- console.log(`
12351
+ process.stdout.write(`
12294
12352
  Total: ${enrichedPlugins.length} plugins`);
12295
12353
  }
12296
12354
  break;
@@ -12327,7 +12385,7 @@ See docs/plugin-guide.md for the publishing workflow.`
12327
12385
  );
12328
12386
  break;
12329
12387
  default:
12330
- console.log(handlePluginHelp());
12388
+ process.stdout.write(handlePluginHelp() + "\n");
12331
12389
  }
12332
12390
  }
12333
12391
  function handleCreate(args, options) {
@@ -12361,7 +12419,7 @@ function handleDaemon(args, options, mode) {
12361
12419
  break;
12362
12420
  }
12363
12421
  default:
12364
- console.log("Daemon starts automatically. No manual action needed.");
12422
+ process.stdout.write("Daemon starts automatically. No manual action needed.\n");
12365
12423
  }
12366
12424
  }
12367
12425
 
@@ -12430,8 +12488,8 @@ async function handleRecord(args, options, mode) {
12430
12488
  }, mode);
12431
12489
  const md = SessionRecorder.readMarkdownSummary(sessionName);
12432
12490
  if (md) {
12433
- console.log("");
12434
- console.log(md);
12491
+ process.stdout.write("\n");
12492
+ process.stdout.write(md + "\n");
12435
12493
  } else {
12436
12494
  const summary = SessionRecorder.readSummary(sessionName);
12437
12495
  if (summary) {
@@ -12490,25 +12548,30 @@ async function handleRecord(args, options, mode) {
12490
12548
  break;
12491
12549
  }
12492
12550
  default:
12493
- console.log("Usage:");
12494
- console.log(" xbrowser record start [--url <url>] [--session <name>]");
12495
- console.log(" xbrowser record stop [--session <name>]");
12496
- console.log(" xbrowser record status [--session <name>]");
12497
- console.log(" xbrowser record summary [--session <name>] [--json]");
12498
- console.log(' xbrowser record checkpoint --type <type> --hint "description" [--selector <sel>] [--session <name>]');
12499
- console.log(" xbrowser record generate-plugin [--session <name>] [--name <plugin>] [--output <dir>]");
12500
- console.log("");
12501
- console.log("Checkpoint types: dialog, captcha, login, iframe, slider, custom");
12551
+ process.stdout.write("Usage:\n");
12552
+ process.stdout.write(" xbrowser record start [--url <url>] [--session <name>]\n");
12553
+ process.stdout.write(" xbrowser record stop [--session <name>]\n");
12554
+ process.stdout.write(" xbrowser record status [--session <name>]\n");
12555
+ process.stdout.write(" xbrowser record summary [--session <name>] [--json]\n");
12556
+ process.stdout.write(' xbrowser record checkpoint --type <type> --hint "description" [--selector <sel>] [--session <name>]\n');
12557
+ process.stdout.write(" xbrowser record generate-plugin [--session <name>] [--name <plugin>] [--output <dir>]\n");
12558
+ process.stdout.write("\n");
12559
+ process.stdout.write("Checkpoint types: dialog, captcha, login, iframe, slider, custom\n");
12502
12560
  }
12503
12561
  }
12504
12562
  function printRecordingSummary(summary, sessionName) {
12505
- console.log("");
12506
- console.log("=== Recording Summary ===");
12507
- console.log(` Start URL: ${summary.startUrl}`);
12508
- console.log(` Duration: ${Math.round(summary.durationMs / 1e3)}s`);
12509
- console.log(` Actions: ${summary.totalActions}`);
12510
- console.log(` Network: ${summary.totalNetworkRequests}`);
12511
- console.log(` Steps: ${summary.steps.length}`);
12563
+ process.stdout.write("\n");
12564
+ process.stdout.write("=== Recording Summary ===\n");
12565
+ process.stdout.write(` Start URL: ${summary.startUrl}
12566
+ `);
12567
+ process.stdout.write(` Duration: ${Math.round(summary.durationMs / 1e3)}s
12568
+ `);
12569
+ process.stdout.write(` Actions: ${summary.totalActions}
12570
+ `);
12571
+ process.stdout.write(` Network: ${summary.totalNetworkRequests}
12572
+ `);
12573
+ process.stdout.write(` Steps: ${summary.steps.length}
12574
+ `);
12512
12575
  for (const step of summary.steps) {
12513
12576
  const a = step.action;
12514
12577
  const el = a.element;
@@ -12530,20 +12593,24 @@ function printRecordingSummary(summary, sessionName) {
12530
12593
  }
12531
12594
  const navInfo = step.contextChanges.find((c) => c.type === "navigate");
12532
12595
  if (navInfo) desc += ` \u2192 navigate to ${navInfo.url?.substring(0, 80)}`;
12533
- console.log(` ${step.step}. ${desc}`);
12596
+ process.stdout.write(` ${step.step}. ${desc}
12597
+ `);
12534
12598
  if (a.clickContext) {
12535
12599
  const ctx = a.clickContext;
12536
12600
  if (ctx.appeared?.length > 0) {
12537
12601
  for (const popup of ctx.appeared) {
12538
12602
  const roleStr = popup.role ? ` [${popup.role}]` : "";
12539
- console.log(` \u21B3 ${popup.tag}${roleStr} "${(popup.text || "").substring(0, 60)}"`);
12603
+ process.stdout.write(` \u21B3 ${popup.tag}${roleStr} "${(popup.text || "").substring(0, 60)}"
12604
+ `);
12540
12605
  if (popup.items?.length > 0) {
12541
12606
  for (const item of popup.items.slice(0, 10)) {
12542
12607
  const disStr = item.disabled ? " [disabled]" : "";
12543
- console.log(` \u2022 ${item.text}${disStr}`);
12608
+ process.stdout.write(` \u2022 ${item.text}${disStr}
12609
+ `);
12544
12610
  }
12545
12611
  if (popup.items.length > 10) {
12546
- console.log(` ... and ${popup.items.length - 10} more items`);
12612
+ process.stdout.write(` ... and ${popup.items.length - 10} more items
12613
+ `);
12547
12614
  }
12548
12615
  }
12549
12616
  }
@@ -12556,31 +12623,41 @@ function printRecordingSummary(summary, sessionName) {
12556
12623
  if (sc.ariaSelected !== void 0) parts.push(`selected=${sc.ariaSelected}`);
12557
12624
  if (sc.dataState) parts.push(`state=${sc.dataState}`);
12558
12625
  if (parts.length > 0) {
12559
- console.log(` \u21B3 state: <${sc.tag}> "${(sc.text || "").substring(0, 30)}" ${parts.join(", ")}`);
12626
+ process.stdout.write(` \u21B3 state: <${sc.tag}> "${(sc.text || "").substring(0, 30)}" ${parts.join(", ")}
12627
+ `);
12560
12628
  }
12561
12629
  }
12562
12630
  }
12563
12631
  }
12564
12632
  }
12565
- console.log("");
12566
- console.log(` Files: ${SessionRecorder.getRecordingsDir(sessionName)}/`);
12633
+ process.stdout.write("\n");
12634
+ process.stdout.write(` Files: ${SessionRecorder.getRecordingsDir(sessionName)}/
12635
+ `);
12567
12636
  if (summary.checkpoints && summary.checkpoints.length > 0) {
12568
- console.log("");
12569
- console.log(` Checkpoints (${summary.checkpoints.length}):`);
12637
+ process.stdout.write("\n");
12638
+ process.stdout.write(` Checkpoints (${summary.checkpoints.length}):
12639
+ `);
12570
12640
  for (const cp of summary.checkpoints) {
12571
12641
  const src = cp.source === "auto" ? "[auto]" : "[manual]";
12572
- console.log(` ${cp.id}. ${src} [${cp.type}] ${cp.hint}`);
12573
- if (cp.selector) console.log(` selector: ${cp.selector}`);
12642
+ process.stdout.write(` ${cp.id}. ${src} [${cp.type}] ${cp.hint}
12643
+ `);
12644
+ if (cp.selector) process.stdout.write(` selector: ${cp.selector}
12645
+ `);
12574
12646
  }
12575
12647
  }
12576
12648
  }
12577
12649
  function printHumanReadableSummary(summary) {
12578
- console.log(`Start URL: ${summary.startUrl}`);
12579
- console.log(`Recorded: ${summary.recordedAt}`);
12580
- console.log(`Duration: ${Math.round(summary.durationMs / 1e3)}s`);
12581
- console.log(`Actions: ${summary.totalActions}`);
12582
- console.log(`Network: ${summary.totalNetworkRequests}`);
12583
- console.log("");
12650
+ process.stdout.write(`Start URL: ${summary.startUrl}
12651
+ `);
12652
+ process.stdout.write(`Recorded: ${summary.recordedAt}
12653
+ `);
12654
+ process.stdout.write(`Duration: ${Math.round(summary.durationMs / 1e3)}s
12655
+ `);
12656
+ process.stdout.write(`Actions: ${summary.totalActions}
12657
+ `);
12658
+ process.stdout.write(`Network: ${summary.totalNetworkRequests}
12659
+ `);
12660
+ process.stdout.write("\n");
12584
12661
  for (const step of summary.steps) {
12585
12662
  const a = step.action;
12586
12663
  const el = a.element;
@@ -12596,20 +12673,23 @@ function printHumanReadableSummary(summary) {
12596
12673
  if (a.value) parts.push(`value="${a.value.substring(0, 50)}"`);
12597
12674
  if (a.key) parts.push(`key=${a.key}`);
12598
12675
  if (a.x !== void 0 && a.y !== void 0) parts.push(`@(${a.x},${a.y})`);
12599
- console.log(parts.join(" "));
12676
+ process.stdout.write(parts.join(" ") + "\n");
12600
12677
  if (a.clickContext) {
12601
12678
  const ctx = a.clickContext;
12602
12679
  if (ctx.appeared?.length > 0) {
12603
12680
  for (const popup of ctx.appeared) {
12604
12681
  const roleStr = popup.role ? ` [${popup.role}]` : "";
12605
- console.log(` \u{1F4CB} ${popup.tag}${roleStr} "${(popup.text || "").substring(0, 60)}"`);
12682
+ process.stdout.write(` \u{1F4CB} ${popup.tag}${roleStr} "${(popup.text || "").substring(0, 60)}"
12683
+ `);
12606
12684
  if (popup.items?.length > 0) {
12607
12685
  for (const item of popup.items.slice(0, 10)) {
12608
12686
  const disStr = item.disabled ? " [disabled]" : "";
12609
- console.log(` \u2022 ${item.text}${disStr}`);
12687
+ process.stdout.write(` \u2022 ${item.text}${disStr}
12688
+ `);
12610
12689
  }
12611
12690
  if (popup.items.length > 10) {
12612
- console.log(` ... and ${popup.items.length - 10} more items`);
12691
+ process.stdout.write(` ... and ${popup.items.length - 10} more items
12692
+ `);
12613
12693
  }
12614
12694
  }
12615
12695
  }
@@ -12622,40 +12702,50 @@ function printHumanReadableSummary(summary) {
12622
12702
  if (sc.ariaSelected !== void 0) stateParts.push(`selected=${sc.ariaSelected}`);
12623
12703
  if (sc.dataState) stateParts.push(`state=${sc.dataState}`);
12624
12704
  if (stateParts.length > 0) {
12625
- console.log(` \u{1F504} <${sc.tag}> "${(sc.text || "").substring(0, 30)}" ${stateParts.join(", ")}`);
12705
+ process.stdout.write(` \u{1F504} <${sc.tag}> "${(sc.text || "").substring(0, 30)}" ${stateParts.join(", ")}
12706
+ `);
12626
12707
  }
12627
12708
  }
12628
12709
  }
12629
12710
  }
12630
12711
  for (const net of step.network) {
12631
- console.log(` \u2192 ${net.method} ${net.path} [${net.status}] ${net.resourceType}`);
12712
+ process.stdout.write(` \u2192 ${net.method} ${net.path} [${net.status}] ${net.resourceType}
12713
+ `);
12632
12714
  if (net.requestBody && typeof net.requestBody === "object") {
12633
12715
  const bodyStr = JSON.stringify(net.requestBody);
12634
12716
  if (bodyStr.length <= 200) {
12635
- console.log(` body: ${bodyStr}`);
12717
+ process.stdout.write(` body: ${bodyStr}
12718
+ `);
12636
12719
  } else {
12637
- console.log(` body: ${bodyStr.substring(0, 200)}... (${bodyStr.length} bytes)`);
12720
+ process.stdout.write(` body: ${bodyStr.substring(0, 200)}... (${bodyStr.length} bytes)
12721
+ `);
12638
12722
  }
12639
12723
  }
12640
12724
  }
12641
12725
  for (const match of step.matchedInputs) {
12642
- console.log(` \u{1F517} input "${match.inputValue}" \u2192 network #${match.networkId} param "${match.paramName}"`);
12726
+ process.stdout.write(` \u{1F517} input "${match.inputValue}" \u2192 network #${match.networkId} param "${match.paramName}"
12727
+ `);
12643
12728
  }
12644
12729
  for (const ctx of step.contextChanges) {
12645
12730
  if (ctx.type === "navigate") {
12646
- console.log(` \u2197 navigate \u2192 ${ctx.url}`);
12731
+ process.stdout.write(` \u2197 navigate \u2192 ${ctx.url}
12732
+ `);
12647
12733
  } else if (ctx.type === "new_tab") {
12648
- console.log(` \u2197 new tab: ${ctx.url}`);
12734
+ process.stdout.write(` \u2197 new tab: ${ctx.url}
12735
+ `);
12649
12736
  }
12650
12737
  }
12651
12738
  }
12652
12739
  if (summary.checkpoints && summary.checkpoints.length > 0) {
12653
- console.log("");
12654
- console.log(`Checkpoints (${summary.checkpoints.length}):`);
12740
+ process.stdout.write("\n");
12741
+ process.stdout.write(`Checkpoints (${summary.checkpoints.length}):
12742
+ `);
12655
12743
  for (const cp of summary.checkpoints) {
12656
12744
  const src = cp.source === "auto" ? "[auto]" : "[manual]";
12657
- console.log(` ${cp.id}. ${src} [${cp.type}] ${cp.hint}`);
12658
- if (cp.selector) console.log(` selector: ${cp.selector}`);
12745
+ process.stdout.write(` ${cp.id}. ${src} [${cp.type}] ${cp.hint}
12746
+ `);
12747
+ if (cp.selector) process.stdout.write(` selector: ${cp.selector}
12748
+ `);
12659
12749
  }
12660
12750
  }
12661
12751
  }
@@ -12681,13 +12771,25 @@ async function handleReplay(args, options, mode) {
12681
12771
  }
12682
12772
  return;
12683
12773
  }
12774
+ if (result.ok && mode !== "json" && mode !== "yaml") {
12775
+ const healed = typeof result.healed === "number" ? result.healed : 0;
12776
+ if (healed > 0) {
12777
+ process.stdout.write(`
12778
+ Self-healed ${healed} action(s):
12779
+ `);
12780
+ for (const d of result.healedDetails ?? []) {
12781
+ process.stdout.write(` - step ${d.index + 1}: ${d.strategy}
12782
+ `);
12783
+ }
12784
+ }
12785
+ }
12684
12786
  outputResult(result, mode);
12685
12787
  }
12686
12788
  async function handleConvert(args, _mode) {
12687
12789
  const filePath = args[0];
12688
12790
  const outputPath = args[1];
12689
12791
  if (!filePath || !outputPath) {
12690
- console.error("Usage: xbrowser convert <recording.yaml> <output.{js,py,sh}>");
12792
+ outputError("Usage: xbrowser convert <recording.yaml> <output.{js,py,sh}>");
12691
12793
  process.exit(1);
12692
12794
  }
12693
12795
  const fs6 = await import("fs");
@@ -12699,11 +12801,11 @@ async function handleConvert(args, _mode) {
12699
12801
  const content = fs6.readFileSync(filePath, "utf-8");
12700
12802
  recording = yaml3.parse(content);
12701
12803
  } catch (e) {
12702
- console.error(`Error: Failed to read "${filePath}": ${e instanceof Error ? e.message.split("\n")[0] : String(e)}`);
12804
+ outputError(`Error: Failed to read "${filePath}": ${e instanceof Error ? e.message.split("\n")[0] : String(e)}`);
12703
12805
  process.exit(1);
12704
12806
  }
12705
12807
  if (recording === null || typeof recording !== "object" || Array.isArray(recording)) {
12706
- console.error(`Error: "${filePath}" does not contain a valid recording (expected a YAML/JSON object with events or actions).`);
12808
+ outputError(`Error: "${filePath}" does not contain a valid recording (expected a YAML/JSON object with events or actions).`);
12707
12809
  process.exit(1);
12708
12810
  }
12709
12811
  const rawActions = recording.actions;
@@ -12737,24 +12839,28 @@ async function handleConvert(args, _mode) {
12737
12839
  fs6.writeFileSync(outputPath, script);
12738
12840
  fs6.chmodSync(outputPath, 493);
12739
12841
  const eventCount = (recordingTyped.events || []).length;
12740
- console.log(`Converted ${filePath} -> ${outputPath}`);
12741
- console.log(` Events: ${eventCount}, Start URL: ${recordingTyped.startUrl}`);
12742
- console.log(` Run: ${ext === ".py" ? "python" : ext === ".sh" ? "./" : "node"} ${outputPath}`);
12842
+ process.stdout.write(`Converted ${filePath} -> ${outputPath}
12843
+ `);
12844
+ process.stdout.write(` Events: ${eventCount}, Start URL: ${recordingTyped.startUrl}
12845
+ `);
12846
+ process.stdout.write(` Run: ${ext === ".py" ? "python" : ext === ".sh" ? "./" : "node"} ${outputPath}
12847
+ `);
12743
12848
  }
12744
12849
  async function handleExtract(args, _mode) {
12745
12850
  const filePath = args[0];
12746
12851
  if (!filePath) {
12747
- console.error("Usage: xbrowser extract <recording.yaml>");
12852
+ outputError("Usage: xbrowser extract <recording.yaml>");
12748
12853
  process.exit(1);
12749
12854
  }
12750
12855
  const { extractAndSave: extractAndSave2, printExtractSummary: printExtractSummary2 } = await import("./extract-DSU2RVMN.js");
12751
12856
  try {
12752
12857
  const { summary, outputPath } = extractAndSave2(filePath);
12753
12858
  printExtractSummary2(summary);
12754
- console.log(`
12755
- Saved LLM summary: ${outputPath}`);
12859
+ process.stdout.write(`
12860
+ Saved LLM summary: ${outputPath}
12861
+ `);
12756
12862
  } catch (e) {
12757
- console.error(`Error: ${e instanceof Error ? e.message : String(e)}`);
12863
+ outputError(`Error: ${e instanceof Error ? e.message : String(e)}`);
12758
12864
  process.exit(1);
12759
12865
  }
12760
12866
  }
@@ -12762,7 +12868,7 @@ async function handleFilter(args, _mode, options) {
12762
12868
  const filePath = args[0];
12763
12869
  const outputPath = args[1];
12764
12870
  if (!filePath || !outputPath) {
12765
- console.error("Usage: xbrowser filter <input.yaml> <output.yaml> [--exclude type1,type2]");
12871
+ outputError("Usage: xbrowser filter <input.yaml> <output.yaml> [--exclude type1,type2]");
12766
12872
  process.exit(1);
12767
12873
  }
12768
12874
  const { filterRecording: filterRecording2, parseExcludeTypes: parseExcludeTypes2 } = await import("./filter-3DXC6432.js");
@@ -12774,15 +12880,17 @@ async function handleFilter(args, _mode, options) {
12774
12880
  const excludeTypes = parseExcludeTypes2(excludeArgs);
12775
12881
  try {
12776
12882
  const result = filterRecording2(filePath, outputPath, excludeTypes);
12777
- console.log(`Filtered ${filePath} -> ${outputPath}`);
12778
- console.log(` Original: ${result.originalCount}, After: ${result.filteredCount}, Removed: ${result.removed} (${result.percentage}%)`);
12883
+ process.stdout.write(`Filtered ${filePath} -> ${outputPath}
12884
+ `);
12885
+ process.stdout.write(` Original: ${result.originalCount}, After: ${result.filteredCount}, Removed: ${result.removed} (${result.percentage}%)
12886
+ `);
12779
12887
  } catch (e) {
12780
- console.error(`Error: ${e instanceof Error ? e.message : String(e)}`);
12888
+ outputError(`Error: ${e instanceof Error ? e.message : String(e)}`);
12781
12889
  process.exit(1);
12782
12890
  }
12783
12891
  }
12784
12892
  async function handleGeneratePlugin(sessionName, pluginName, outputDir) {
12785
- const { SessionRecorder: SessionRecorder2 } = await import("./session-recorder-SLDBENVF.js");
12893
+ const { SessionRecorder: SessionRecorder2 } = await import("./session-recorder-563ISSJK.js");
12786
12894
  const { readSiteKnowledge: readSiteKnowledge2, toMarkdown } = await import("./site-knowledge-SYC6VCDB.js");
12787
12895
  const { mkdirSync: mkdirSync11, writeFileSync: writeFileSync13 } = await import("fs");
12788
12896
  const { join: join16 } = await import("path");
@@ -12806,21 +12914,30 @@ async function handleGeneratePlugin(sessionName, pluginName, outputDir) {
12806
12914
  if (knowledgeMd) {
12807
12915
  writeFileSync13(join16(finalOutputDir, "SITE_KNOWLEDGE.md"), knowledgeMd, "utf-8");
12808
12916
  }
12809
- console.log("");
12810
- console.log("=== Plugin Generated ===");
12811
- console.log(` Plugin: ${finalPluginName}`);
12812
- console.log(` Domain: ${domain}`);
12813
- console.log(` Output: ${finalOutputDir}/index.ts`);
12917
+ process.stdout.write("\n");
12918
+ process.stdout.write("=== Plugin Generated ===\n");
12919
+ process.stdout.write(` Plugin: ${finalPluginName}
12920
+ `);
12921
+ process.stdout.write(` Domain: ${domain}
12922
+ `);
12923
+ process.stdout.write(` Output: ${finalOutputDir}/index.ts
12924
+ `);
12814
12925
  if (knowledgeMd) {
12815
- console.log(` Knowledge: ${finalOutputDir}/SITE_KNOWLEDGE.md`);
12926
+ process.stdout.write(` Knowledge: ${finalOutputDir}/SITE_KNOWLEDGE.md
12927
+ `);
12816
12928
  }
12817
- console.log(` Actions: ${data.actions.length}`);
12818
- console.log(` APIs: ${data.network.filter((n) => n.contentType.includes("json") || n.url.includes("/api/")).length}`);
12819
- console.log("");
12820
- console.log("Next steps:");
12821
- console.log(` 1. Review and edit: ${finalOutputDir}/index.ts`);
12822
- console.log(` 2. Test: xbrowser ${finalPluginName} <command>`);
12823
- console.log(` 3. Reference: ${finalOutputDir}/SITE_KNOWLEDGE.md (for LLM)`);
12929
+ process.stdout.write(` Actions: ${data.actions.length}
12930
+ `);
12931
+ process.stdout.write(` APIs: ${data.network.filter((n) => n.contentType.includes("json") || n.url.includes("/api/")).length}
12932
+ `);
12933
+ process.stdout.write("\n");
12934
+ process.stdout.write("Next steps:\n");
12935
+ process.stdout.write(` 1. Review and edit: ${finalOutputDir}/index.ts
12936
+ `);
12937
+ process.stdout.write(` 2. Test: xbrowser ${finalPluginName} <command>
12938
+ `);
12939
+ process.stdout.write(` 3. Reference: ${finalOutputDir}/SITE_KNOWLEDGE.md (for LLM)
12940
+ `);
12824
12941
  }
12825
12942
  function generatePluginCode(pluginName, domain, data, _knowledgeMd) {
12826
12943
  const pagePaths = /* @__PURE__ */ new Set();
@@ -12999,20 +13116,22 @@ async function handleRun(filePath, options) {
12999
13116
  });
13000
13117
  for (const step of chainResult.steps) {
13001
13118
  if (step.success) {
13002
- console.log(`[OK] ${step.raw}`);
13119
+ process.stdout.write(`[OK] ${step.raw}
13120
+ `);
13003
13121
  if (step.data && typeof step.data === "object") {
13004
13122
  const d = step.data;
13005
13123
  for (const [k, v] of Object.entries(d)) {
13006
13124
  if (k !== "ok")
13007
- console.log(` ${k}: ${typeof v === "string" ? v : JSON.stringify(v)}`);
13125
+ process.stdout.write(` ${k}: ${typeof v === "string" ? v : JSON.stringify(v)}
13126
+ `);
13008
13127
  }
13009
13128
  }
13010
13129
  } else {
13011
- console.error(`[FAIL] ${step.raw}: ${step.message}`);
13130
+ outputError(`[FAIL] ${step.raw}: ${step.message}`);
13012
13131
  }
13013
13132
  }
13014
13133
  if (chainResult.stoppedReason) {
13015
- console.error(`Stopped: ${chainResult.stoppedReason}`);
13134
+ outputError(`Stopped: ${chainResult.stoppedReason}`);
13016
13135
  }
13017
13136
  if (!chainResult.success) process.exit(1);
13018
13137
  }
@@ -13055,26 +13174,29 @@ async function handleNetCommand(args, options, mode, sessionName) {
13055
13174
  if (mode === "json") {
13056
13175
  outputResult(result, mode);
13057
13176
  } else {
13058
- console.log(`
13177
+ process.stdout.write(`
13059
13178
  Network captures (session: ${netSession})`);
13060
- console.log(` Total: ${result.total}, Showing: ${result.captures.length}
13179
+ process.stdout.write(` Total: ${result.total}, Showing: ${result.captures.length}
13061
13180
  `);
13062
13181
  for (const c of result.captures) {
13063
13182
  const statusColor = c.status < 300 ? "\x1B[32m" : c.status < 400 ? "\x1B[33m" : "\x1B[31m";
13064
13183
  const reset = "\x1B[0m";
13065
- console.log(` #${c.id} ${c.method.padEnd(6)} ${statusColor}${c.status}${reset} ${c.resourceType.padEnd(10)} ${c.path}`);
13184
+ process.stdout.write(` #${c.id} ${c.method.padEnd(6)} ${statusColor}${c.status}${reset} ${c.resourceType.padEnd(10)} ${c.path}
13185
+ `);
13066
13186
  if (c.size > 0) {
13067
13187
  const sizeStr = c.size > 1024 ? `${(c.size / 1024).toFixed(1)}KB` : `${c.size}B`;
13068
- console.log(` ${c.contentType.split(";")[0]} ${sizeStr}`);
13188
+ process.stdout.write(` ${c.contentType.split(";")[0]} ${sizeStr}
13189
+ `);
13069
13190
  }
13070
13191
  }
13071
- console.log("");
13192
+ process.stdout.write("\n");
13072
13193
  }
13073
13194
  break;
13074
13195
  }
13075
13196
  case "clear": {
13076
13197
  await forwardNetworkClear(netSession);
13077
- console.log(`Network captures cleared for session: ${netSession}`);
13198
+ process.stdout.write(`Network captures cleared for session: ${netSession}
13199
+ `);
13078
13200
  break;
13079
13201
  }
13080
13202
  case "top": {
@@ -13084,21 +13206,23 @@ async function handleNetCommand(args, options, mode, sessionName) {
13084
13206
  if (mode === "json") {
13085
13207
  outputResult(result, mode);
13086
13208
  } else {
13087
- console.log(`
13209
+ process.stdout.write(`
13088
13210
  Top valued requests (session: ${netSession})`);
13089
- console.log(` Showing: ${result.entries.length}
13211
+ process.stdout.write(` Showing: ${result.entries.length}
13090
13212
  `);
13091
13213
  for (const e of result.entries) {
13092
13214
  const scoreColor = e.score >= 50 ? "\x1B[32m" : e.score >= 20 ? "\x1B[33m" : "\x1B[90m";
13093
13215
  const reset = "\x1B[0m";
13094
13216
  const methodStr = e.method.padEnd(6);
13095
13217
  const scoreStr = `${scoreColor}${e.score.toString().padStart(3)}${reset}`;
13096
- console.log(` ${scoreStr} ${methodStr} ${e.status} ${e.resourceType.padEnd(10)} ${e.path}`);
13218
+ process.stdout.write(` ${scoreStr} ${methodStr} ${e.status} ${e.resourceType.padEnd(10)} ${e.path}
13219
+ `);
13097
13220
  if (e.scoreBreakdown.content > 0) {
13098
- console.log(` ${e.contentType.split(";")[0]} ${e.size > 1024 ? (e.size / 1024).toFixed(1) + "KB" : e.size + "B"}`);
13221
+ process.stdout.write(` ${e.contentType.split(";")[0]} ${e.size > 1024 ? (e.size / 1024).toFixed(1) + "KB" : e.size + "B"}
13222
+ `);
13099
13223
  }
13100
13224
  }
13101
- console.log("");
13225
+ process.stdout.write("\n");
13102
13226
  }
13103
13227
  break;
13104
13228
  }
@@ -13107,16 +13231,17 @@ async function handleNetCommand(args, options, mode, sessionName) {
13107
13231
  if (mode === "json") {
13108
13232
  outputResult(logResult, mode);
13109
13233
  } else {
13110
- console.log(`
13234
+ process.stdout.write(`
13111
13235
  Command log (session: ${netSession})`);
13112
- console.log(` Total: ${logResult.commands.length}
13236
+ process.stdout.write(` Total: ${logResult.commands.length}
13113
13237
  `);
13114
13238
  for (const cmd of logResult.commands) {
13115
13239
  const ts = new Date(cmd.timestamp).toISOString().substring(11, 19);
13116
13240
  const paramsStr = Object.entries(cmd.params).map(([k, v]) => `${k}=${v}`).join(" ");
13117
- console.log(` #${cmd.id} [${ts}] ${cmd.command} ${paramsStr}`);
13241
+ process.stdout.write(` #${cmd.id} [${ts}] ${cmd.command} ${paramsStr}
13242
+ `);
13118
13243
  }
13119
- console.log("");
13244
+ process.stdout.write("\n");
13120
13245
  }
13121
13246
  break;
13122
13247
  }
@@ -13132,28 +13257,31 @@ async function handleNetCommand(args, options, mode, sessionName) {
13132
13257
  outputResult(aroundResult, mode);
13133
13258
  } else {
13134
13259
  if (!aroundResult) {
13135
- console.log(" No command found with that ID");
13260
+ process.stdout.write(" No command found with that ID\n");
13136
13261
  break;
13137
13262
  }
13138
13263
  const cmd = aroundResult.command;
13139
13264
  const ts = new Date(cmd.timestamp).toISOString().substring(11, 19);
13140
- console.log(`
13265
+ process.stdout.write(`
13141
13266
  Command: #${cmd.id} [${ts}] ${cmd.command}`);
13142
- console.log(` Window: \xB1${windowMs}ms
13267
+ process.stdout.write(` Window: \xB1${windowMs}ms
13143
13268
  `);
13144
13269
  const before = aroundResult.before;
13145
13270
  const after = aroundResult.after;
13146
- console.log(` BEFORE (${before.length} requests):`);
13271
+ process.stdout.write(` BEFORE (${before.length} requests):
13272
+ `);
13147
13273
  for (const r of before.slice(0, 5)) {
13148
- console.log(` ${r.method} ${r.status} ${String(r.resourceType).padEnd(10)} ${r.path}`);
13274
+ process.stdout.write(` ${r.method} ${r.status} ${String(r.resourceType).padEnd(10)} ${r.path}
13275
+ `);
13149
13276
  }
13150
- console.log(`
13277
+ process.stdout.write(`
13151
13278
  AFTER (${aroundResult.afterCount} requests):`);
13152
13279
  for (const r of after.slice(0, 10)) {
13153
13280
  const highlight = r.method !== "GET" ? " \u2190" : "";
13154
- console.log(` ${String(r.method).padEnd(6)} ${r.status} ${String(r.resourceType).padEnd(10)} ${r.path}${highlight}`);
13281
+ process.stdout.write(` ${String(r.method).padEnd(6)} ${r.status} ${String(r.resourceType).padEnd(10)} ${r.path}${highlight}
13282
+ `);
13155
13283
  }
13156
- console.log("");
13284
+ process.stdout.write("\n");
13157
13285
  }
13158
13286
  break;
13159
13287
  }
@@ -13162,9 +13290,9 @@ async function handleNetCommand(args, options, mode, sessionName) {
13162
13290
  if (mode === "json") {
13163
13291
  outputResult(result, mode);
13164
13292
  } else {
13165
- console.log(`
13293
+ process.stdout.write(`
13166
13294
  API Reusability Analysis (session: ${netSession})`);
13167
- console.log(` Total: ${result.total}, Analyzed: ${result.analyzed.length}
13295
+ process.stdout.write(` Total: ${result.total}, Analyzed: ${result.analyzed.length}
13168
13296
  `);
13169
13297
  const groups = { high: [], medium: [], low: [], unknown: [] };
13170
13298
  for (const e of result.analyzed) {
@@ -13175,16 +13303,20 @@ async function handleNetCommand(args, options, mode, sessionName) {
13175
13303
  if (!items?.length) continue;
13176
13304
  const color = level === "high" ? "\x1B[32m" : level === "medium" ? "\x1B[33m" : level === "low" ? "\x1B[31m" : "\x1B[90m";
13177
13305
  const reset = "\x1B[0m";
13178
- console.log(` ${color}${level.toUpperCase()}${reset} (${items.length})`);
13306
+ process.stdout.write(` ${color}${level.toUpperCase()}${reset} (${items.length})
13307
+ `);
13179
13308
  for (const e of items.slice(0, 5)) {
13180
13309
  const scoreStr = `[${e.reusability.score.toString().padStart(3)}]`;
13181
- console.log(` ${e.method.padEnd(6)} ${e.status} ${scoreStr} ${e.path}`);
13310
+ process.stdout.write(` ${e.method.padEnd(6)} ${e.status} ${scoreStr} ${e.path}
13311
+ `);
13182
13312
  if (e.reusability.reasons.length > 0) {
13183
- console.log(` ${e.reusability.reasons.join(", ")}`);
13313
+ process.stdout.write(` ${e.reusability.reasons.join(", ")}
13314
+ `);
13184
13315
  }
13185
13316
  }
13186
- if (items.length > 5) console.log(` ... and ${items.length - 5} more`);
13187
- console.log("");
13317
+ if (items.length > 5) process.stdout.write(` ... and ${items.length - 5} more
13318
+ `);
13319
+ process.stdout.write("\n");
13188
13320
  }
13189
13321
  }
13190
13322
  break;
@@ -13203,12 +13335,12 @@ async function handleNetCommand(args, options, mode, sessionName) {
13203
13335
  if (mode === "json") {
13204
13336
  outputResult(result, mode);
13205
13337
  } else {
13206
- console.log(`
13338
+ process.stdout.write(`
13207
13339
  ${result.method} ${result.url}`);
13208
- console.log(` Headers: ${result.headerCount}, Body: ${result.hasBody}
13340
+ process.stdout.write(` Headers: ${result.headerCount}, Body: ${result.hasBody}
13209
13341
  `);
13210
- console.log(result.command);
13211
- console.log("");
13342
+ process.stdout.write(result.command + "\n");
13343
+ process.stdout.write("\n");
13212
13344
  }
13213
13345
  break;
13214
13346
  }
@@ -13226,27 +13358,33 @@ async function handleNetCommand(args, options, mode, sessionName) {
13226
13358
  if (mode === "json") {
13227
13359
  outputResult(result, mode);
13228
13360
  } else {
13229
- console.log(`
13361
+ process.stdout.write(`
13230
13362
  Replay Result`);
13231
- console.log(` ${result.curlCommand?.split("\n")[0]?.trim()}
13363
+ process.stdout.write(` ${result.curlCommand?.split("\n")[0]?.trim()}
13232
13364
  `);
13233
13365
  const replay = result.replay;
13234
13366
  if (replay?.error) {
13235
- console.log(` \x1B[31mFAILED\x1B[0m: ${replay.error}`);
13367
+ process.stdout.write(` \x1B[31mFAILED\x1B[0m: ${replay.error}
13368
+ `);
13236
13369
  } else if (replay) {
13237
13370
  const statusColor = replay.status && replay.status < 300 ? "\x1B[32m" : "\x1B[31m";
13238
13371
  const status = replay.status;
13239
13372
  const size = replay.size;
13240
13373
  const duration = replay.duration;
13241
- console.log(` Status: ${statusColor}${status}\x1B[0m ${replay.statusText}`);
13242
- console.log(` Size: ${size > 1024 ? (size / 1024).toFixed(1) + "KB" : size + "B"}`);
13243
- console.log(` Duration: ${duration}ms`);
13244
- console.log(` Body Match: ${replay.bodyMatch ? "\x1B[32mYes\x1B[0m" : "\x1B[33mNo\x1B[0m"}`);
13374
+ process.stdout.write(` Status: ${statusColor}${status}\x1B[0m ${replay.statusText}
13375
+ `);
13376
+ process.stdout.write(` Size: ${size > 1024 ? (size / 1024).toFixed(1) + "KB" : size + "B"}
13377
+ `);
13378
+ process.stdout.write(` Duration: ${duration}ms
13379
+ `);
13380
+ process.stdout.write(` Body Match: ${replay.bodyMatch ? "\x1B[32mYes\x1B[0m" : "\x1B[33mNo\x1B[0m"}
13381
+ `);
13245
13382
  if (status && status >= 400) {
13246
- console.log(` \x1B[33m\u26A0 API may require fresh signature/token\x1B[0m`);
13383
+ process.stdout.write(` \x1B[33m\u26A0 API may require fresh signature/token\x1B[0m
13384
+ `);
13247
13385
  }
13248
13386
  }
13249
- console.log("");
13387
+ process.stdout.write("\n");
13250
13388
  }
13251
13389
  break;
13252
13390
  }
@@ -13265,40 +13403,47 @@ async function handleNetCommand(args, options, mode, sessionName) {
13265
13403
  outputResult(result, mode);
13266
13404
  } else {
13267
13405
  const c = result.capture;
13268
- console.log(`
13406
+ process.stdout.write(`
13269
13407
  Request #${c.id}`);
13270
- console.log(` ${c.method} ${c.url}`);
13271
- console.log(` Status: ${c.status} | Size: ${c.size}B | Type: ${c.contentType}`);
13272
- console.log(` Resource: ${c.resourceType}`);
13408
+ process.stdout.write(` ${c.method} ${c.url}
13409
+ `);
13410
+ process.stdout.write(` Status: ${c.status} | Size: ${c.size}B | Type: ${c.contentType}
13411
+ `);
13412
+ process.stdout.write(` Resource: ${c.resourceType}
13413
+ `);
13273
13414
  if (c.requestHeaders) {
13274
- console.log(`
13415
+ process.stdout.write(`
13275
13416
  Request Headers:`);
13276
13417
  for (const [k, v] of Object.entries(c.requestHeaders)) {
13277
- console.log(` ${k}: ${String(v).substring(0, 100)}`);
13418
+ process.stdout.write(` ${k}: ${String(v).substring(0, 100)}
13419
+ `);
13278
13420
  }
13279
13421
  }
13280
13422
  if (c.requestBody !== void 0) {
13281
- console.log(`
13423
+ process.stdout.write(`
13282
13424
  Request Body:`);
13283
13425
  const bodyStr = typeof c.requestBody === "string" ? c.requestBody : JSON.stringify(c.requestBody, null, 2);
13284
13426
  const lines = bodyStr.split("\n").slice(0, 20);
13285
- for (const line of lines) console.log(` ${line}`);
13286
- if (bodyStr.split("\n").length > 20) console.log(" ...");
13427
+ for (const line of lines) process.stdout.write(` ${line}
13428
+ `);
13429
+ if (bodyStr.split("\n").length > 20) process.stdout.write(" ...");
13287
13430
  }
13288
- console.log(`
13431
+ process.stdout.write(`
13289
13432
  Response Headers:`);
13290
13433
  for (const [k, v] of Object.entries(c.headers)) {
13291
- console.log(` ${k}: ${String(v).substring(0, 100)}`);
13434
+ process.stdout.write(` ${k}: ${String(v).substring(0, 100)}
13435
+ `);
13292
13436
  }
13293
13437
  if (c.body !== void 0) {
13294
- console.log(`
13438
+ process.stdout.write(`
13295
13439
  Response Body:`);
13296
13440
  const bodyStr = typeof c.body === "string" ? c.body : JSON.stringify(c.body, null, 2);
13297
13441
  const lines = bodyStr.split("\n").slice(0, 20);
13298
- for (const line of lines) console.log(` ${line}`);
13299
- if (bodyStr.split("\n").length > 20) console.log(" ...");
13442
+ for (const line of lines) process.stdout.write(` ${line}
13443
+ `);
13444
+ if (bodyStr.split("\n").length > 20) process.stdout.write(" ...");
13300
13445
  }
13301
- console.log("");
13446
+ process.stdout.write("\n");
13302
13447
  }
13303
13448
  break;
13304
13449
  }
@@ -13309,7 +13454,8 @@ async function handleNetCommand(args, options, mode, sessionName) {
13309
13454
  break;
13310
13455
  }
13311
13456
  await forwardNetworkLike(netSession, id);
13312
- console.log(`Marked #${id} as useful`);
13457
+ process.stdout.write(`Marked #${id} as useful
13458
+ `);
13313
13459
  break;
13314
13460
  }
13315
13461
  case "dislike": {
@@ -13319,7 +13465,8 @@ async function handleNetCommand(args, options, mode, sessionName) {
13319
13465
  break;
13320
13466
  }
13321
13467
  await forwardNetworkDislike(netSession, id);
13322
- console.log(`Marked #${id} as not useful`);
13468
+ process.stdout.write(`Marked #${id} as not useful
13469
+ `);
13323
13470
  break;
13324
13471
  }
13325
13472
  case "export": {
@@ -13334,7 +13481,7 @@ async function handleNetCommand(args, options, mode, sessionName) {
13334
13481
  outputError(result.error);
13335
13482
  break;
13336
13483
  }
13337
- console.log(result.code);
13484
+ process.stdout.write(result.code + "\n");
13338
13485
  break;
13339
13486
  }
13340
13487
  default:
@@ -13346,9 +13493,25 @@ async function handleNetCommand(args, options, mode, sessionName) {
13346
13493
  }
13347
13494
 
13348
13495
  // src/cli/test-routes.ts
13349
- import { execSync as execSync3 } from "child_process";
13496
+ import { execFile as execFile3 } from "child_process";
13350
13497
  import { readFileSync as readFileSync11 } from "fs";
13351
13498
  import { resolve as resolve9 } from "path";
13499
+ function execCli(args) {
13500
+ return new Promise((resolvePromise) => {
13501
+ execFile3(
13502
+ "npx",
13503
+ args,
13504
+ { timeout: 65e3, env: { ...process.env, FORCE_COLOR: "0" } },
13505
+ (err, stdout, stderr) => {
13506
+ resolvePromise({
13507
+ stdout: typeof stdout === "string" ? stdout : stdout?.toString("utf-8") ?? "",
13508
+ stderr: typeof stderr === "string" ? stderr : stderr?.toString("utf-8") ?? "",
13509
+ error: err ?? null
13510
+ });
13511
+ }
13512
+ );
13513
+ });
13514
+ }
13352
13515
  function findPluginPath(plugin) {
13353
13516
  const candidates = [
13354
13517
  resolve9(process.cwd(), ".xcli/plugins", plugin, "index.ts"),
@@ -13413,20 +13576,21 @@ function extractSchema(plugin, command) {
13413
13576
  }
13414
13577
  async function runTest(plugin, command, cmdArgs, options) {
13415
13578
  const cdp = options.cdp || options.cdpEndpoint || "http://localhost:9221";
13416
- const argsStr = cmdArgs.filter((a) => !a.startsWith("--cdp")).join(" ");
13579
+ const passthroughArgs = cmdArgs.filter((a) => !a.startsWith("--cdp"));
13417
13580
  const schema = extractSchema(plugin, command);
13418
- const fullCmd = `npx xbrowser ${plugin} ${command} ${argsStr} --cdp ${cdp} --json --timeout 60000`;
13419
- let stdout = "";
13420
- try {
13421
- stdout = execSync3(fullCmd, {
13422
- timeout: 65e3,
13423
- encoding: "utf-8",
13424
- stdio: ["pipe", "pipe", "pipe"],
13425
- env: { ...process.env, FORCE_COLOR: "0" }
13426
- });
13427
- } catch (e) {
13428
- const err = e;
13429
- stdout = err.stdout?.toString() || "";
13581
+ const cliArgs = [
13582
+ "xbrowser",
13583
+ plugin,
13584
+ command,
13585
+ ...passthroughArgs,
13586
+ "--cdp",
13587
+ String(cdp),
13588
+ "--json",
13589
+ "--timeout",
13590
+ "60000"
13591
+ ];
13592
+ const { stdout, stderr, error } = await execCli(cliArgs);
13593
+ if (error) {
13430
13594
  const jsonLine = stdout.split("\n").find((l) => {
13431
13595
  try {
13432
13596
  JSON.parse(l);
@@ -13445,11 +13609,10 @@ async function runTest(plugin, command, cmdArgs, options) {
13445
13609
  } catch {
13446
13610
  }
13447
13611
  }
13448
- const stderr = err.stderr?.toString() || "";
13449
13612
  if (stdout.includes("captcha") || stderr.includes("captcha") || stdout.includes("CAPTCHA")) {
13450
13613
  return { status: "CAPTCHA", message: "\u68C0\u6D4B\u5230\u9A8C\u8BC1\u7801", viewerUrl: "http://localhost:9224/preview/default" };
13451
13614
  }
13452
- return { status: "EXEC_ERROR", message: (err.message || "").slice(0, 200) || "\u6267\u884C\u5931\u8D25" };
13615
+ return { status: "EXEC_ERROR", message: (error.message || "").slice(0, 200) || "\u6267\u884C\u5931\u8D25" };
13453
13616
  }
13454
13617
  const allLines = stdout.split("\n");
13455
13618
  const jsonStart = allLines.findIndex((l) => l.trim().startsWith("{"));
@@ -13519,27 +13682,27 @@ async function handleTest(cmdArgs, options, mode, cdpEndpoint) {
13519
13682
  const plugin = cmdArgs[0];
13520
13683
  const command = cmdArgs[1];
13521
13684
  if (!plugin || !command) {
13522
- console.error("\u7528\u6CD5: xbrowser test <plugin> <command> [\u53C2\u6570...]");
13523
- console.error("\u793A\u4F8B: xbrowser test doubao list --cdp 9221");
13685
+ outputError("\u7528\u6CD5: xbrowser test <plugin> <command> [\u53C2\u6570...]");
13686
+ outputError("\u793A\u4F8B: xbrowser test doubao list --cdp 9221");
13524
13687
  return;
13525
13688
  }
13526
13689
  const loader = await getPluginLoader();
13527
13690
  const internalLoader = loader.getCore().loader;
13528
13691
  const site = internalLoader.getSite(plugin);
13529
13692
  if (!site) {
13530
- console.error(`\u63D2\u4EF6 "${plugin}" \u4E0D\u5B58\u5728`);
13693
+ outputError(`\u63D2\u4EF6 "${plugin}" \u4E0D\u5B58\u5728`);
13531
13694
  return;
13532
13695
  }
13533
13696
  const cmdEntry = site.getCommand(command);
13534
13697
  if (!cmdEntry) {
13535
- console.error(`\u6307\u4EE4 "${command}" \u4E0D\u5B58\u5728`);
13698
+ outputError(`\u6307\u4EE4 "${command}" \u4E0D\u5B58\u5728`);
13536
13699
  return;
13537
13700
  }
13538
13701
  const testArgs = cmdArgs.slice(2);
13539
13702
  const mergedOptions = { ...options, cdp: cdpEndpoint || options.cdp };
13540
13703
  const result = await runTest(plugin, command, testArgs, mergedOptions);
13541
13704
  if (mode === "json") {
13542
- console.log(JSON.stringify(result, null, 2));
13705
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
13543
13706
  return;
13544
13707
  }
13545
13708
  const r = result;
@@ -13554,23 +13717,32 @@ async function handleTest(cmdArgs, options, mode, cdpEndpoint) {
13554
13717
  };
13555
13718
  const status = String(r.status);
13556
13719
  const icon = icons[status] || "\u2753";
13557
- console.log(`
13558
- ${icon} ${plugin}.${command}`);
13559
- console.log(` \u72B6\u6001: ${status}`);
13720
+ process.stdout.write(`
13721
+ ${icon} ${plugin}.${command}
13722
+ `);
13723
+ process.stdout.write(` \u72B6\u6001: ${status}
13724
+ `);
13560
13725
  if (status === "OK") {
13561
- if (r.count) console.log(` \u6570\u636E: ${r.count} \u9879`);
13562
- if (r.data) console.log(` \u9884\u89C8: ${String(r.data).slice(0, 150)}`);
13726
+ if (r.count) process.stdout.write(` \u6570\u636E: ${r.count} \u9879
13727
+ `);
13728
+ if (r.data) process.stdout.write(` \u9884\u89C8: ${String(r.data).slice(0, 150)}
13729
+ `);
13563
13730
  } else if (status === "LOGIN_REQUIRED" || status === "CAPTCHA") {
13564
- console.log(` \u4FE1\u606F: ${String(r.message)}`);
13565
- console.log(` Viewer: ${String(r.viewerUrl)}`);
13731
+ process.stdout.write(` \u4FE1\u606F: ${String(r.message)}
13732
+ `);
13733
+ process.stdout.write(` Viewer: ${String(r.viewerUrl)}`);
13566
13734
  } else if (status === "SCHEMA_ERROR") {
13567
13735
  const errs = r.errors;
13568
- if (errs) console.log(` \u9519\u8BEF: ${errs.join("; ")}`);
13736
+ if (errs) process.stdout.write(` \u9519\u8BEF: ${errs.join("; ")}
13737
+ `);
13569
13738
  } else if (["NO_DATA", "BLOCKED"].includes(status)) {
13570
- console.log(` \u4FE1\u606F: ${String(r.message)}`);
13571
- if (r.viewerUrl) console.log(` Viewer: ${String(r.viewerUrl)}`);
13739
+ process.stdout.write(` \u4FE1\u606F: ${String(r.message)}
13740
+ `);
13741
+ if (r.viewerUrl) process.stdout.write(` Viewer: ${String(r.viewerUrl)}
13742
+ `);
13572
13743
  } else {
13573
- console.log(` \u4FE1\u606F: ${String(r.message)}`);
13744
+ process.stdout.write(` \u4FE1\u606F: ${String(r.message)}
13745
+ `);
13574
13746
  }
13575
13747
  }
13576
13748
 
@@ -13668,7 +13840,7 @@ Commands:
13668
13840
  plugin list List plugins
13669
13841
  plugin reload <name> Reload plugin
13670
13842
  create <name> --template <type> Create plugin
13671
- serve [--port <port>] [--token <t>] Start HTTP server
13843
+ serve [--port <p>] [--host <h>] [--token <t>] [--cors-origins <csv>] Start HTTP server (loopback by default)
13672
13844
  remote <url> [command] [--token <t>] Execute on remote server
13673
13845
  run <file> Execute commands from file
13674
13846
  viewer [--name <n>] Generate viewer URL
@@ -13789,10 +13961,26 @@ function validateAuth(authHeader, validTokens) {
13789
13961
  function isAuthRequired(validTokens) {
13790
13962
  return validTokens.length > 0;
13791
13963
  }
13964
+ var IPV6_LOOPBACK = /* @__PURE__ */ new Set(["::1", "0:0:0:0:0:0:0:1"]);
13965
+ function isLoopbackHost(host) {
13966
+ const h = host.trim().toLowerCase().replace(/^\[/, "").replace(/\]$/, "");
13967
+ if (h === "localhost") return true;
13968
+ if (IPV6_LOOPBACK.has(h)) return true;
13969
+ const ipv4 = h.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/);
13970
+ return ipv4 !== null && ipv4[1] === "127";
13971
+ }
13972
+ function isLoopbackOrigin(origin) {
13973
+ try {
13974
+ const url = new URL(origin);
13975
+ if (url.protocol !== "http:" && url.protocol !== "https:") return false;
13976
+ return isLoopbackHost(url.hostname);
13977
+ } catch {
13978
+ return false;
13979
+ }
13980
+ }
13792
13981
 
13793
13982
  // src/server/router.ts
13794
- var CORS_HEADERS = {
13795
- "Access-Control-Allow-Origin": "*",
13983
+ var CORS_BASE_HEADERS = {
13796
13984
  "Access-Control-Allow-Methods": "GET, POST, DELETE, OPTIONS",
13797
13985
  "Access-Control-Allow-Headers": "Content-Type, Authorization"
13798
13986
  };
@@ -13845,9 +14033,31 @@ function jsonResponse(statusCode, body, extraHeaders) {
13845
14033
  return {
13846
14034
  statusCode,
13847
14035
  body,
13848
- headers: { ...CORS_HEADERS, "Content-Type": "application/json", ...extraHeaders }
14036
+ headers: { "Content-Type": "application/json", ...extraHeaders }
14037
+ };
14038
+ }
14039
+ function corsHeadersFor(origin, explicitOrigins) {
14040
+ if (!origin || !isOriginAllowed(origin, explicitOrigins)) return {};
14041
+ return {
14042
+ "Access-Control-Allow-Origin": origin,
14043
+ Vary: "Origin",
14044
+ ...CORS_BASE_HEADERS
13849
14045
  };
13850
14046
  }
14047
+ function isOriginAllowed(origin, explicitOrigins) {
14048
+ if (explicitOrigins && explicitOrigins.length > 0) {
14049
+ return explicitOrigins.includes("*") || explicitOrigins.includes(origin);
14050
+ }
14051
+ return isLoopbackOrigin(origin);
14052
+ }
14053
+ function isJsonContentType(contentType) {
14054
+ return contentType.toLowerCase().split(";")[0].trim() === "application/json";
14055
+ }
14056
+ function applyCorsHeaders(response, origin, corsOrigins) {
14057
+ const cors = corsHeadersFor(origin, corsOrigins);
14058
+ if (Object.keys(cors).length === 0) return response;
14059
+ return { ...response, headers: { ...cors, ...response.headers ?? {} } };
14060
+ }
13851
14061
  function errorResponse(statusCode, error, message) {
13852
14062
  return jsonResponse(statusCode, { error, message, statusCode });
13853
14063
  }
@@ -13965,13 +14175,30 @@ async function ensureSession(sessionName, url, cdpEndpoint) {
13965
14175
  function isHealthCheckPath(pathname) {
13966
14176
  return pathname === "/api/v1/health";
13967
14177
  }
13968
- async function route(method, url, headers, body) {
14178
+ async function route(method, url, headers, body, corsOrigins) {
14179
+ const response = await routeInner(method, url, headers, body, corsOrigins);
14180
+ return applyCorsHeaders(response, headers.origin, corsOrigins);
14181
+ }
14182
+ async function routeInner(method, url, headers, body, corsOrigins) {
13969
14183
  const parsedUrl = new URL(url, "http://localhost");
13970
14184
  const pathname = parsedUrl.pathname;
13971
14185
  const query = parseQueryString(parsedUrl.search);
13972
14186
  if (method === "OPTIONS") {
13973
14187
  return jsonResponse(204, null);
13974
14188
  }
14189
+ if (headers.origin && !isOriginAllowed(headers.origin, corsOrigins)) {
14190
+ return errorResponse(403, "FORBIDDEN", `Origin ${headers.origin} is not allowed to call this API`);
14191
+ }
14192
+ if (method === "POST" || method === "PUT") {
14193
+ const contentType = headers["content-type"];
14194
+ if (contentType && !isJsonContentType(contentType)) {
14195
+ return errorResponse(
14196
+ 415,
14197
+ "UNSUPPORTED_MEDIA_TYPE",
14198
+ `Content-Type must be application/json, got "${contentType}"`
14199
+ );
14200
+ }
14201
+ }
13975
14202
  const match = matchRoute(method, pathname);
13976
14203
  if (!match) {
13977
14204
  const pathMatch = matchRoute("GET", pathname) || matchRoute("POST", pathname) || matchRoute("DELETE", pathname);
@@ -13994,19 +14221,24 @@ async function route(method, url, headers, body) {
13994
14221
  return errorResponse(500, "INTERNAL_ERROR", errMsg(err));
13995
14222
  }
13996
14223
  }
13997
- async function handleRequest(req, res, validateAuthFn) {
14224
+ async function handleRequest(req, res, validateAuthFn, corsOrigins) {
13998
14225
  const url = req.url || "/";
13999
14226
  const method = (req.method || "GET").toUpperCase();
14000
14227
  const pathname = new URL(url, "http://localhost").pathname;
14001
14228
  if (method === "OPTIONS") {
14002
- const response2 = await route(method, url, headersToObject(req.headers), null);
14229
+ const response2 = await route(method, url, headersToObject(req.headers), null, corsOrigins);
14003
14230
  writeResponse(res, response2);
14004
14231
  return;
14005
14232
  }
14006
14233
  if (validateAuthFn && !isHealthCheckPath(pathname)) {
14007
14234
  const authHeader = req.headers["authorization"];
14008
14235
  if (!validateAuthFn(authHeader)) {
14009
- writeResponse(res, errorResponse(401, "UNAUTHORIZED", "Invalid or missing authentication token"));
14236
+ const denied = applyCorsHeaders(
14237
+ errorResponse(401, "UNAUTHORIZED", "Invalid or missing authentication token"),
14238
+ req.headers.origin,
14239
+ corsOrigins
14240
+ );
14241
+ writeResponse(res, denied);
14010
14242
  return;
14011
14243
  }
14012
14244
  }
@@ -14014,7 +14246,7 @@ async function handleRequest(req, res, validateAuthFn) {
14014
14246
  if (method === "POST" || method === "PUT") {
14015
14247
  body = await readBody(req);
14016
14248
  }
14017
- const response = await route(method, url, headersToObject(req.headers), body);
14249
+ const response = await route(method, url, headersToObject(req.headers), body, corsOrigins);
14018
14250
  writeResponse(res, response);
14019
14251
  }
14020
14252
  function headersToObject(headers) {
@@ -14055,26 +14287,35 @@ var HTTPServer = class {
14055
14287
  host;
14056
14288
  server = null;
14057
14289
  validTokens;
14290
+ corsOrigins;
14058
14291
  constructor(config) {
14059
14292
  this.port = config?.port ?? 9224;
14060
- this.host = config?.host ?? "0.0.0.0";
14293
+ this.host = config?.host ?? "127.0.0.1";
14061
14294
  this.validTokens = resolveTokens(config?.tokens);
14295
+ this.corsOrigins = config?.corsOrigins;
14062
14296
  }
14063
14297
  /**
14064
14298
  * Start the HTTP server and begin listening for requests.
14065
14299
  *
14066
14300
  * @returns The actual port and host the server bound to.
14067
- * @throws If the server is already running or fails to start.
14301
+ * @throws If the server is already running, if binding a non-loopback
14302
+ * host without an auth token, or if the port cannot be bound.
14068
14303
  */
14069
14304
  async start() {
14070
14305
  if (this.server) {
14071
14306
  throw new Error("HTTP server is already running");
14072
14307
  }
14308
+ if (!isLoopbackHost(this.host) && !isAuthRequired(this.validTokens)) {
14309
+ throw new Error(
14310
+ `Refusing to start on non-loopback host "${this.host}" without an auth token \u2014 the API exposes session creation and command execution. Bind loopback only (the default, or --host 127.0.0.1), or provide a token via --token or XBROWSER_SERVER_TOKEN.`
14311
+ );
14312
+ }
14073
14313
  const authRequired = isAuthRequired(this.validTokens);
14074
14314
  const tokens = this.validTokens;
14315
+ const corsOrigins = this.corsOrigins;
14075
14316
  this.server = createServer((req, res) => {
14076
14317
  const authFn = authRequired ? (authHeader) => validateAuth(authHeader, tokens) : void 0;
14077
- handleRequest(req, res, authFn).catch((err) => {
14318
+ handleRequest(req, res, authFn, corsOrigins).catch((err) => {
14078
14319
  const message = err instanceof Error ? err.message : String(err);
14079
14320
  if (!res.headersSent) {
14080
14321
  res.writeHead(500, { "Content-Type": "application/json" });
@@ -14545,7 +14786,7 @@ async function routeCommand(argvIn, stdinCommands) {
14545
14786
  replay: "replay <file> [--slow-mo <ms>] [--stop-on-error]",
14546
14787
  create: "create <name> [--template static|dynamic|login|api]",
14547
14788
  run: "run <file>",
14548
- serve: "serve [--port <port>] [--token <token>]",
14789
+ serve: "serve [--port <port>] [--host <host>] [--token <token>] [--cors-origins <csv>]",
14549
14790
  remote: "remote <url> [command] [--token <token>]",
14550
14791
  convert: "convert <file> [--to js|py|sh]",
14551
14792
  extract: "extract <file> [--format json|yaml]",
@@ -14822,7 +15063,7 @@ Run "xbrowser ${command} ${subCommand} --help" to see available parameters.`
14822
15063
  const targetPage = pages[cmdTabIndex];
14823
15064
  await targetPage.bringToFront().catch(() => {
14824
15065
  });
14825
- const { setActivePage: setActivePage2 } = await import("./browser-VZOOFLAH.js");
15066
+ const { setActivePage: setActivePage2 } = await import("./browser-Q67BJTI2.js");
14826
15067
  setActivePage2(session, targetPage);
14827
15068
  }
14828
15069
  }
@@ -14944,7 +15185,10 @@ Run "xbrowser ${command} ${subCommand} --help" to see available parameters.`
14944
15185
  async function handleServe(_args, options, mode) {
14945
15186
  const port = options.port ? Number(options.port) : void 0;
14946
15187
  const token = options.token;
14947
- const httpServer = new HTTPServer({ port, tokens: token ? [token] : void 0 });
15188
+ const host = options.host;
15189
+ const corsOriginsRaw = options["cors-origins"];
15190
+ const corsOrigins = corsOriginsRaw ? corsOriginsRaw.split(",").map((s) => s.trim()).filter(Boolean) : void 0;
15191
+ const httpServer = new HTTPServer({ port, host, tokens: token ? [token] : void 0, corsOrigins });
14948
15192
  process.on("SIGINT", async () => {
14949
15193
  await httpServer.stop();
14950
15194
  return;
@@ -17995,7 +18239,7 @@ var DataCollector = class {
17995
18239
  return results;
17996
18240
  }
17997
18241
  async createBrowserContext() {
17998
- const { launch } = await import("./cdp-driver-26LGCHXV.js");
18242
+ const { launch } = await import("./cdp-driver-WWO2UZDR.js");
17999
18243
  const { browser } = await launch({
18000
18244
  headless: true,
18001
18245
  args: ["--no-sandbox", "--disable-setuid-sandbox"]