cbrowser 6.3.1 → 6.4.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
@@ -10,11 +10,12 @@ const browser_js_1 = require("./browser.js");
10
10
  const personas_js_1 = require("./personas.js");
11
11
  const types_js_1 = require("./types.js");
12
12
  const mcp_server_js_1 = require("./mcp-server.js");
13
+ const daemon_js_1 = require("./daemon.js");
13
14
  function showHelp() {
14
15
  console.log(`
15
16
  ╔══════════════════════════════════════════════════════════════════════════════╗
16
- ║ CBrowser CLI v6.3.0 ║
17
- ║ AI-powered browser automation with flaky test detection
17
+ ║ CBrowser CLI v6.4.0 ║
18
+ ║ AI-powered browser automation with performance regression detection
18
19
  ╚══════════════════════════════════════════════════════════════════════════════╝
19
20
 
20
21
  NAVIGATION
@@ -147,6 +148,27 @@ PERFORMANCE
147
148
  --budget-fcp <ms> FCP budget (default: 1800)
148
149
  --budget-cls <score> CLS budget (default: 0.1)
149
150
 
151
+ PERFORMANCE REGRESSION (v6.4.0)
152
+ perf-baseline save <url> Capture and save performance baseline
153
+ --name <name> Human-readable name for baseline
154
+ --runs <n> Number of runs to average (default: 3)
155
+ Examples:
156
+ cbrowser perf-baseline save "https://example.com" --name "homepage"
157
+ cbrowser perf-baseline save "https://example.com/checkout" --runs 5
158
+
159
+ perf-baseline list List all saved baselines
160
+ perf-baseline show <name> Show baseline details
161
+ perf-baseline delete <name> Delete a baseline
162
+
163
+ perf-regression <url> <baseline> Compare current performance against baseline
164
+ --threshold-lcp <n> Max LCP increase % (default: 20)
165
+ --threshold-cls <n> Max CLS increase (default: 0.1)
166
+ --threshold-fcp <n> Max FCP increase % (default: 20)
167
+ --output <file> Save JSON report to file
168
+ Examples:
169
+ cbrowser perf-regression "https://example.com" homepage
170
+ cbrowser perf-regression "https://example.com" homepage --threshold-lcp 30
171
+
150
172
  NETWORK / HAR
151
173
  har start Start recording HAR
152
174
  har stop [output] Stop and save HAR file
@@ -259,6 +281,16 @@ MCP SERVER (v5.0.0)
259
281
  mcp-server Start CBrowser as MCP server for Claude integration
260
282
  Use with Claude Desktop or other MCP-compatible clients
261
283
 
284
+ DAEMON MODE (v6.4.0)
285
+ daemon start Start background daemon (keeps browser running)
286
+ --port <port> Daemon port (default: 9222)
287
+ --timeout <min> Idle timeout in minutes (default: 30)
288
+ daemon stop Stop the running daemon
289
+ daemon status Check if daemon is running
290
+ daemon run Run daemon in foreground (internal use)
291
+ Note: When daemon is running, all commands automatically connect to it
292
+ instead of launching a new browser - much faster for iteration!
293
+
262
294
  STORAGE & CLEANUP
263
295
  storage Show storage usage statistics
264
296
  cleanup Clean up old files
@@ -661,6 +693,90 @@ async function main() {
661
693
  await (0, mcp_server_js_1.startMcpServer)();
662
694
  return;
663
695
  }
696
+ // Daemon mode commands
697
+ if (command === "daemon") {
698
+ const subCommand = args[0];
699
+ const port = parseInt(options.port) || 9222;
700
+ switch (subCommand) {
701
+ case "start": {
702
+ console.log("🚀 Starting CBrowser daemon...");
703
+ const result = await (0, daemon_js_1.startDaemon)(port);
704
+ console.log(result.success ? `✓ ${result.message}` : `✗ ${result.message}`);
705
+ process.exit(result.success ? 0 : 1);
706
+ break;
707
+ }
708
+ case "stop": {
709
+ console.log("🛑 Stopping CBrowser daemon...");
710
+ const result = await (0, daemon_js_1.stopDaemon)();
711
+ console.log(result.success ? `✓ ${result.message}` : `✗ ${result.message}`);
712
+ process.exit(0);
713
+ break;
714
+ }
715
+ case "status": {
716
+ const status = await (0, daemon_js_1.getDaemonStatus)();
717
+ console.log(status);
718
+ process.exit(0);
719
+ break;
720
+ }
721
+ case "run": {
722
+ // Internal: run daemon in foreground
723
+ console.log("🔧 Running daemon in foreground mode...");
724
+ const browserType = options.browser === "firefox" ? "firefox"
725
+ : options.browser === "webkit" ? "webkit"
726
+ : "chromium";
727
+ // runDaemonServer will merge with defaults internally
728
+ await (0, daemon_js_1.runDaemonServer)({
729
+ browser: browserType,
730
+ headless: options.headless !== false && options.headless !== "false",
731
+ }, port);
732
+ return;
733
+ }
734
+ default:
735
+ console.error(`Unknown daemon command: ${subCommand}`);
736
+ console.error("Use: daemon start | daemon stop | daemon status");
737
+ process.exit(1);
738
+ }
739
+ return;
740
+ }
741
+ // Check if daemon is running and use it for supported commands
742
+ const daemonRunning = await (0, daemon_js_1.isDaemonRunning)();
743
+ if (daemonRunning && ["navigate", "click", "fill", "screenshot", "extract", "run"].includes(command)) {
744
+ console.log("🔌 Connected to running daemon");
745
+ let daemonCommand = command;
746
+ let daemonArgs = {};
747
+ switch (command) {
748
+ case "navigate":
749
+ daemonArgs = { url: args[0] };
750
+ break;
751
+ case "click":
752
+ daemonArgs = { selector: args[0] };
753
+ break;
754
+ case "fill":
755
+ daemonArgs = { selector: args[0], value: args[1] };
756
+ break;
757
+ case "screenshot":
758
+ daemonArgs = { path: args[0] };
759
+ break;
760
+ case "extract":
761
+ daemonArgs = { what: args[0] };
762
+ break;
763
+ case "run":
764
+ daemonArgs = { command: args.join(" ") };
765
+ break;
766
+ }
767
+ const result = await (0, daemon_js_1.sendToDaemon)(daemonCommand, daemonArgs);
768
+ if (result.success) {
769
+ console.log("✓ Command executed via daemon");
770
+ if (result.result) {
771
+ console.log(JSON.stringify(result.result, null, 2));
772
+ }
773
+ }
774
+ else {
775
+ console.error(`✗ Daemon error: ${result.error}`);
776
+ process.exit(1);
777
+ }
778
+ return;
779
+ }
664
780
  // Parse browser type
665
781
  const browserType = options.browser === "firefox" ? "firefox"
666
782
  : options.browser === "webkit" ? "webkit"
@@ -2551,6 +2667,182 @@ async function main() {
2551
2667
  }
2552
2668
  break;
2553
2669
  }
2670
+ // =========================================================================
2671
+ // Performance Regression Detection (Tier 6 - v6.4.0)
2672
+ // =========================================================================
2673
+ case "perf-baseline": {
2674
+ const subcommand = args[0];
2675
+ const fs = await import("fs");
2676
+ switch (subcommand) {
2677
+ case "save": {
2678
+ const url = args[1];
2679
+ if (!url) {
2680
+ console.error("Usage: cbrowser perf-baseline save <url> [--name <name>] [--runs <n>]");
2681
+ process.exit(1);
2682
+ }
2683
+ console.log(`\n📊 Capturing performance baseline for: ${url}`);
2684
+ const baselineOptions = {
2685
+ headless,
2686
+ name: options.name,
2687
+ runs: options.runs ? parseInt(options.runs) : 3,
2688
+ };
2689
+ console.log(` Running ${baselineOptions.runs} measurement(s)...`);
2690
+ const baseline = await (0, browser_js_1.capturePerformanceBaseline)(url, baselineOptions);
2691
+ console.log(`\n✅ Baseline saved: ${baseline.name}`);
2692
+ console.log(` ID: ${baseline.id}`);
2693
+ console.log(` URL: ${baseline.url}`);
2694
+ console.log(` Timestamp: ${new Date(baseline.timestamp).toLocaleString()}`);
2695
+ console.log(`\n📈 Metrics (averaged over ${baseline.runsAveraged} runs):`);
2696
+ if (baseline.metrics.lcp !== undefined) {
2697
+ console.log(` LCP: ${baseline.metrics.lcp.toFixed(0)}ms (${baseline.metrics.lcpRating})`);
2698
+ }
2699
+ if (baseline.metrics.fcp !== undefined) {
2700
+ console.log(` FCP: ${baseline.metrics.fcp.toFixed(0)}ms`);
2701
+ }
2702
+ if (baseline.metrics.cls !== undefined) {
2703
+ console.log(` CLS: ${baseline.metrics.cls.toFixed(3)} (${baseline.metrics.clsRating})`);
2704
+ }
2705
+ if (baseline.metrics.ttfb !== undefined) {
2706
+ console.log(` TTFB: ${baseline.metrics.ttfb.toFixed(0)}ms`);
2707
+ }
2708
+ if (baseline.metrics.tti !== undefined) {
2709
+ console.log(` TTI: ${baseline.metrics.tti.toFixed(0)}ms`);
2710
+ }
2711
+ if (baseline.metrics.transferSize !== undefined) {
2712
+ console.log(` Transfer: ${(baseline.metrics.transferSize / 1024).toFixed(1)}KB`);
2713
+ }
2714
+ break;
2715
+ }
2716
+ case "list": {
2717
+ const baselines = (0, browser_js_1.listPerformanceBaselines)();
2718
+ if (baselines.length === 0) {
2719
+ console.log("\n📊 No performance baselines saved yet.");
2720
+ console.log(" Use: cbrowser perf-baseline save <url> --name <name>");
2721
+ }
2722
+ else {
2723
+ console.log(`\n📊 Performance Baselines (${baselines.length}):\n`);
2724
+ for (const b of baselines) {
2725
+ const date = new Date(b.timestamp).toLocaleDateString();
2726
+ const lcp = b.metrics.lcp ? `LCP: ${b.metrics.lcp.toFixed(0)}ms` : "";
2727
+ console.log(` ${b.name}`);
2728
+ console.log(` ID: ${b.id}`);
2729
+ console.log(` URL: ${b.url}`);
2730
+ console.log(` Date: ${date} | ${lcp}`);
2731
+ console.log("");
2732
+ }
2733
+ }
2734
+ break;
2735
+ }
2736
+ case "show": {
2737
+ const name = args[1];
2738
+ if (!name) {
2739
+ console.error("Usage: cbrowser perf-baseline show <name|id>");
2740
+ process.exit(1);
2741
+ }
2742
+ const baseline = (0, browser_js_1.loadPerformanceBaseline)(name);
2743
+ if (!baseline) {
2744
+ console.error(`Baseline not found: ${name}`);
2745
+ process.exit(1);
2746
+ }
2747
+ console.log(`\n📊 Performance Baseline: ${baseline.name}`);
2748
+ console.log(` ID: ${baseline.id}`);
2749
+ console.log(` URL: ${baseline.url}`);
2750
+ console.log(` Timestamp: ${new Date(baseline.timestamp).toLocaleString()}`);
2751
+ console.log(` Runs Averaged: ${baseline.runsAveraged}`);
2752
+ console.log(`\n📈 Metrics:`);
2753
+ console.log(JSON.stringify(baseline.metrics, null, 2));
2754
+ console.log(`\n🖥️ Environment:`);
2755
+ console.log(JSON.stringify(baseline.environment, null, 2));
2756
+ break;
2757
+ }
2758
+ case "delete": {
2759
+ const name = args[1];
2760
+ if (!name) {
2761
+ console.error("Usage: cbrowser perf-baseline delete <name|id>");
2762
+ process.exit(1);
2763
+ }
2764
+ const deleted = (0, browser_js_1.deletePerformanceBaseline)(name);
2765
+ if (deleted) {
2766
+ console.log(`\n✅ Deleted baseline: ${name}`);
2767
+ }
2768
+ else {
2769
+ console.error(`Baseline not found: ${name}`);
2770
+ process.exit(1);
2771
+ }
2772
+ break;
2773
+ }
2774
+ default:
2775
+ console.error("Usage: cbrowser perf-baseline <save|list|show|delete>");
2776
+ console.error("");
2777
+ console.error("Subcommands:");
2778
+ console.error(" save <url> Capture and save performance baseline");
2779
+ console.error(" list List all saved baselines");
2780
+ console.error(" show <name> Show baseline details");
2781
+ console.error(" delete <name> Delete a baseline");
2782
+ process.exit(1);
2783
+ }
2784
+ break;
2785
+ }
2786
+ case "perf-regression": {
2787
+ const url = args[0];
2788
+ const baselineName = args[1];
2789
+ if (!url || !baselineName) {
2790
+ console.error("Usage: cbrowser perf-regression <url> <baseline-name> [options]");
2791
+ console.error("");
2792
+ console.error("Options:");
2793
+ console.error(" --threshold-lcp <n> Max LCP increase % (default: 20)");
2794
+ console.error(" --threshold-cls <n> Max CLS increase absolute (default: 0.1)");
2795
+ console.error(" --threshold-fcp <n> Max FCP increase % (default: 20)");
2796
+ console.error(" --threshold-ttfb <n> Max TTFB increase % (default: 30)");
2797
+ console.error(" --output <file> Save JSON report to file");
2798
+ console.error("");
2799
+ console.error("Examples:");
2800
+ console.error(" cbrowser perf-regression https://example.com homepage");
2801
+ console.error(" cbrowser perf-regression https://example.com homepage --threshold-lcp 30");
2802
+ process.exit(1);
2803
+ }
2804
+ console.log(`\n🔍 Checking for performance regressions...`);
2805
+ console.log(` URL: ${url}`);
2806
+ console.log(` Baseline: ${baselineName}`);
2807
+ const thresholds = {};
2808
+ if (options["threshold-lcp"])
2809
+ thresholds.lcp = parseInt(options["threshold-lcp"]);
2810
+ if (options["threshold-cls"])
2811
+ thresholds.cls = parseFloat(options["threshold-cls"]);
2812
+ if (options["threshold-fcp"])
2813
+ thresholds.fcp = parseInt(options["threshold-fcp"]);
2814
+ if (options["threshold-ttfb"])
2815
+ thresholds.ttfb = parseInt(options["threshold-ttfb"]);
2816
+ if (options["threshold-tti"])
2817
+ thresholds.tti = parseInt(options["threshold-tti"]);
2818
+ if (options["threshold-tbt"])
2819
+ thresholds.tbt = parseInt(options["threshold-tbt"]);
2820
+ const regressionOptions = {
2821
+ headless,
2822
+ thresholds: Object.keys(thresholds).length > 0 ? thresholds : undefined,
2823
+ };
2824
+ try {
2825
+ const result = await (0, browser_js_1.detectPerformanceRegression)(url, baselineName, regressionOptions);
2826
+ // Print formatted report
2827
+ const report = (0, browser_js_1.formatPerformanceRegressionReport)(result);
2828
+ console.log("\n" + report);
2829
+ // Save JSON report if requested
2830
+ const fs = await import("fs");
2831
+ if (options.output) {
2832
+ fs.writeFileSync(options.output, JSON.stringify(result, null, 2));
2833
+ console.log(`\n📄 JSON report saved: ${options.output}`);
2834
+ }
2835
+ // Exit with error code if regressions found
2836
+ if (!result.passed) {
2837
+ process.exit(1);
2838
+ }
2839
+ }
2840
+ catch (error) {
2841
+ console.error(`\n❌ Error: ${error.message}`);
2842
+ process.exit(1);
2843
+ }
2844
+ break;
2845
+ }
2554
2846
  default:
2555
2847
  console.error(`Unknown command: ${command}`);
2556
2848
  console.error("Run 'cbrowser help' for usage");