cbrowser 19.4.2 → 19.5.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.
package/dist/cli.js CHANGED
@@ -29,6 +29,7 @@ import { getStatusInfo, formatStatus, getDataDir, getPaths } from "./config.js";
29
29
  import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, unlinkSync, writeFileSync } from "node:fs";
30
30
  import { resolveCookieDomain, hostFromUrl } from "./utils.js";
31
31
  import { dirname, join, resolve } from "node:path";
32
+ import { guardConsole, guardTerminalOutput } from "./terminal-safe.js";
32
33
  import { VideoCaptureSession } from "./recording/engine.js";
33
34
  import { changeSignal, parseManifest } from "./recording/types.js";
34
35
  import { printEnterpriseStatus } from "./stealth/index.js";
@@ -339,10 +340,16 @@ PERFORMANCE REGRESSION (v6.4.0)
339
340
  cbrowser perf-regression "https://example.com" homepage --sensitivity strict
340
341
  cbrowser perf-regression "https://example.com" homepage --threshold-lcp 30
341
342
 
342
- NETWORK / HAR
343
- har start Start recording HAR
343
+ NETWORK / HAR (need the daemon: each command is its own process)
344
+ daemon start Start the daemon first; it holds the browser between commands
345
+ har start Start recording HAR in the daemon's browser
344
346
  har stop [output] Stop and save HAR file
345
- network list List captured network requests
347
+ network list List network requests the daemon's browser captured
348
+ network clear Clear that list
349
+ console List the daemon tab's console messages (errors, warnings, logs)
350
+ --filter <type> all | error | warning | log | info (default all)
351
+ --clear Empty the console buffer after reading
352
+ --json Print the raw messages as JSON
346
353
 
347
354
  VISUAL REGRESSION (v2.5.0)
348
355
  visual save <name> Save baseline screenshot
@@ -2612,6 +2619,12 @@ async function main() {
2612
2619
  const outputOpts = detectOutputOptions(options);
2613
2620
  setOutputOptions(outputOpts);
2614
2621
  installOutputWrappers();
2622
+ // Page-derived text (console messages, titles, extracted text) reaches stdout in
2623
+ // many places; escape sequences and bidi overrides in it come out as visible
2624
+ // escapes instead of acting on the terminal. (2026-10-12)
2625
+ guardTerminalOutput(process.stdout);
2626
+ guardTerminalOutput(process.stderr);
2627
+ guardConsole();
2615
2628
  // --json-output must never leave stdout empty. Only two commands in this file
2616
2629
  // call emitJson, so without this every other command printed nothing at all and
2617
2630
  // still exited 0 — a CI step piping to jq saw empty input beside a success code.
@@ -4381,32 +4394,122 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
4381
4394
  // =========================================================================
4382
4395
  case "har": {
4383
4396
  const subcommand = args[0];
4384
- switch (subcommand) {
4385
- case "start": {
4386
- browser.startHarRecording();
4387
- console.log("✓ HAR recording started");
4388
- console.log(" Navigate and interact with pages, then run 'cbrowser har stop'");
4389
- break;
4397
+ if (subcommand !== "start" && subcommand !== "stop") {
4398
+ console.error("Usage: cbrowser har [start|stop]");
4399
+ break;
4400
+ }
4401
+ // A recording has to span several commands, and each cbrowser command is
4402
+ // its own process: only the daemon's browser outlives them. Recorded here,
4403
+ // start, navigate and stop were three processes and every HAR was empty,
4404
+ // with or without a daemon, while this printed "started" and "saved".
4405
+ // (2026-10-12)
4406
+ if (!(await isDaemonRunning())) {
4407
+ console.error("Error: HAR recording spans several commands, and each cbrowser command is its own process,");
4408
+ console.error(" so nothing records between them. Start the daemon first:");
4409
+ console.error(" cbrowser daemon start -> cbrowser har start -> navigate/click... -> cbrowser har stop [file]");
4410
+ process.exit(1);
4411
+ }
4412
+ const response = subcommand === "start"
4413
+ ? await sendToDaemon("harStart", {})
4414
+ : await sendToDaemon("harStop", {});
4415
+ if (!response.success) {
4416
+ console.error(`✗ Daemon error: ${response.error}`);
4417
+ process.exit(1);
4418
+ }
4419
+ if (subcommand === "start") {
4420
+ console.log("✓ HAR recording started in the daemon");
4421
+ console.log(" Navigate and interact with pages, then run 'cbrowser har stop'");
4422
+ }
4423
+ else {
4424
+ // The daemon writes only its own har directory; a chosen path is written here,
4425
+ // with this process's permissions.
4426
+ const { file, har } = response.result;
4427
+ if (args[1]) {
4428
+ const out = resolve(args[1]);
4429
+ writeFileSync(out, har);
4430
+ console.log(`✓ HAR saved: ${out}`);
4390
4431
  }
4391
- case "stop": {
4392
- const output = args[1];
4393
- const filename = await browser.exportHar(output);
4394
- console.log(`✓ HAR saved: ${filename}`);
4395
- break;
4432
+ else {
4433
+ console.log(`✓ HAR saved: ${file}`);
4396
4434
  }
4397
- default:
4398
- console.error("Usage: cbrowser har [start|stop]");
4399
4435
  }
4400
- break;
4436
+ process.exit(0);
4401
4437
  }
4402
4438
  // =========================================================================
4403
4439
  // Network
4404
4440
  // =========================================================================
4441
+ case "console": {
4442
+ // The daemon tab's console (log, info, warning, error, uncaught exceptions,
4443
+ // failed resources), as get_console_messages returns it over MCP. Served by
4444
+ // the daemon: a fresh process has no tab and would always list nothing.
4445
+ const filter = typeof options.filter === "string" ? options.filter : "all";
4446
+ if (!["all", "error", "warning", "log", "info"].includes(filter)) {
4447
+ console.error(`Error: --filter must be one of all, error, warning, log, info (got "${filter}")`);
4448
+ process.exit(1);
4449
+ }
4450
+ if (!(await isDaemonRunning())) {
4451
+ console.error("Error: the console log only spans commands in the daemon's browser; each cbrowser command is");
4452
+ console.error(" its own process. Start the daemon first:");
4453
+ console.error(" cbrowser daemon start -> navigate/click... -> cbrowser console [--filter error] [--clear] [--json]");
4454
+ process.exit(1);
4455
+ }
4456
+ const response = await sendToDaemon("consoleMessages", { filter, clear: options.clear === true });
4457
+ if (!response.success) {
4458
+ console.error(`✗ Daemon error: ${response.error}`);
4459
+ process.exit(1);
4460
+ }
4461
+ const out = response.result;
4462
+ if (options.json === true) {
4463
+ console.log(JSON.stringify(out, null, 2));
4464
+ }
4465
+ else if (out.count === 0) {
4466
+ console.log(filter === "all" ? "No console messages" : `No ${filter} console messages`);
4467
+ }
4468
+ else {
4469
+ console.log(`\n🖥 Console (${out.count}${out.dropped ? `, ${out.dropped} older dropped` : ""}):\n`);
4470
+ for (const m of out.messages) {
4471
+ const where = m.url ? ` (${m.url}${m.lineNumber !== undefined ? `:${m.lineNumber}` : ""})` : "";
4472
+ console.log(` [${m.type}${m.source === "pageerror" ? ", uncaught" : ""}] ${m.text}${where}`);
4473
+ console.log(` on ${m.pageUrl}`);
4474
+ }
4475
+ }
4476
+ if (options.clear === true)
4477
+ console.log("✓ Console cleared");
4478
+ process.exit(0);
4479
+ }
4405
4480
  case "network": {
4406
4481
  const subcommand = args[0];
4482
+ if (subcommand !== "list" && subcommand !== "clear") {
4483
+ console.error("Usage: cbrowser network [list|clear]");
4484
+ break;
4485
+ }
4486
+ // Same reason as har: the request log lives in a browser, and only the
4487
+ // daemon's browser outlives a command. A fresh process always listed
4488
+ // nothing and said "Navigate to a page first", even right after a
4489
+ // navigate. (2026-10-12)
4490
+ if (!(await isDaemonRunning())) {
4491
+ console.error("Error: the network log only spans commands in the daemon's browser; each cbrowser command is");
4492
+ console.error(" its own process. Start the daemon first:");
4493
+ console.error(" cbrowser daemon start -> navigate/click... -> cbrowser network list");
4494
+ process.exit(1);
4495
+ }
4496
+ if (subcommand === "clear") {
4497
+ const cleared = await sendToDaemon("networkClear", {});
4498
+ if (!cleared.success) {
4499
+ console.error(`✗ Daemon error: ${cleared.error}`);
4500
+ process.exit(1);
4501
+ }
4502
+ console.log("✓ Network history cleared");
4503
+ process.exit(0);
4504
+ }
4505
+ const listed = await sendToDaemon("networkList", {});
4506
+ if (!listed.success) {
4507
+ console.error(`✗ Daemon error: ${listed.error}`);
4508
+ process.exit(1);
4509
+ }
4407
4510
  switch (subcommand) {
4408
4511
  case "list": {
4409
- const requests = browser.getNetworkRequests();
4512
+ const requests = listed.result.requests;
4410
4513
  if (requests.length === 0) {
4411
4514
  console.log("No network requests captured");
4412
4515
  console.log("Navigate to a page first to capture requests");
@@ -4423,15 +4526,8 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
4423
4526
  }
4424
4527
  break;
4425
4528
  }
4426
- case "clear": {
4427
- browser.clearNetworkHistory();
4428
- console.log("✓ Network history cleared");
4429
- break;
4430
- }
4431
- default:
4432
- console.error("Usage: cbrowser network [list|clear]");
4433
4529
  }
4434
- break;
4530
+ process.exit(0);
4435
4531
  }
4436
4532
  // =========================================================================
4437
4533
  // Visual Regression (Tier 2)