@ulpi/browse 2.3.1 → 2.3.3

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 (2) hide show
  1. package/dist/browse.cjs +368 -319
  2. package/package.json +1 -1
package/dist/browse.cjs CHANGED
@@ -864,7 +864,7 @@ var require_package = __commonJS({
864
864
  "package.json"(exports2, module2) {
865
865
  module2.exports = {
866
866
  name: "@ulpi/browse",
867
- version: "2.3.0",
867
+ version: "2.3.3",
868
868
  homepage: "https://browse.ulpi.io",
869
869
  repository: {
870
870
  type: "git",
@@ -1089,13 +1089,19 @@ __export(bridge_exports, {
1089
1089
  installAdb: () => installAdb
1090
1090
  });
1091
1091
  function resolveDriverApkPath() {
1092
- const localBuild = path7.resolve(
1093
- __dirname2,
1094
- "../../../browse-android/app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk"
1095
- );
1096
- if (fs7.existsSync(localBuild)) return localBuild;
1097
- const installed = path7.resolve(__dirname2, "../../bin/browse-android.apk");
1098
- if (fs7.existsSync(installed)) return installed;
1092
+ const candidates = [
1093
+ // 1. Local dev build
1094
+ path7.resolve(__dirname2, "../../../browse-android/app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk"),
1095
+ // 2. Installed alongside source (bin/ at project root)
1096
+ path7.resolve(__dirname2, "../../bin/browse-android.apk"),
1097
+ // 3. Bundled build (dist/browse.cjs → ../bin/)
1098
+ path7.resolve(__dirname2, "../bin/browse-android.apk"),
1099
+ // 4. Same directory as binary
1100
+ path7.resolve(__dirname2, "browse-android.apk")
1101
+ ];
1102
+ for (const p of candidates) {
1103
+ if (fs7.existsSync(p)) return p;
1104
+ }
1099
1105
  const lazyPath = path7.join(
1100
1106
  process.env.BROWSE_LOCAL_DIR || path7.join(process.cwd(), ".browse"),
1101
1107
  "bin",
@@ -1103,9 +1109,21 @@ function resolveDriverApkPath() {
1103
1109
  );
1104
1110
  if (fs7.existsSync(lazyPath)) return lazyPath;
1105
1111
  throw new Error(
1106
- "browse-android APK not found. Build it with:\n cd browse-android && ./gradlew :app:assembleDebugAndroidTest\nOr run: browse doctor --platform android"
1112
+ "browse-android APK not found. Run: browse enable android\nOr build manually: cd browse-android && ./gradlew :app:assembleDebugAndroidTest"
1107
1113
  );
1108
1114
  }
1115
+ function resolveAppApkPath() {
1116
+ const candidates = [
1117
+ path7.resolve(__dirname2, "../../../browse-android/app/build/outputs/apk/debug/app-debug.apk"),
1118
+ path7.resolve(__dirname2, "../../bin/browse-android-app.apk"),
1119
+ path7.resolve(__dirname2, "../bin/browse-android-app.apk"),
1120
+ path7.resolve(__dirname2, "browse-android-app.apk")
1121
+ ];
1122
+ for (const p of candidates) {
1123
+ if (fs7.existsSync(p)) return p;
1124
+ }
1125
+ return null;
1126
+ }
1109
1127
  function adbExec(serial, ...args) {
1110
1128
  return (0, import_child_process3.execSync)(["adb", "-s", serial, ...args].join(" "), {
1111
1129
  encoding: "utf-8",
@@ -1242,7 +1260,7 @@ function installDriverApk(serial, apkPath) {
1242
1260
  if (installedBase?.includes(`package:${DRIVER_PACKAGE}`) && installedTest?.includes(DRIVER_TEST_PACKAGE)) {
1243
1261
  return;
1244
1262
  }
1245
- const appApkPath = apkPath.replace("-androidTest", "");
1263
+ const appApkPath = resolveAppApkPath() || apkPath.replace("-androidTest", "");
1246
1264
  if (fs7.existsSync(appApkPath)) {
1247
1265
  try {
1248
1266
  adbExec(serial, "install", "-t", "-r", `"${appApkPath}"`);
@@ -2486,7 +2504,12 @@ async function startIOS(opts = {}) {
2486
2504
  } catch {
2487
2505
  }
2488
2506
  }
2489
- const runnerDir = path10.resolve(__dirname_svc, "../../../browse-ios-runner");
2507
+ const runnerCandidates = [
2508
+ path10.resolve(__dirname_svc, "../../../browse-ios-runner"),
2509
+ path10.resolve(__dirname_svc, "../../browse-ios-runner"),
2510
+ path10.resolve(__dirname_svc, "../bin/browse-ios-runner")
2511
+ ];
2512
+ const runnerDir = runnerCandidates.find((d) => fs10.existsSync(path10.join(d, "project.yml"))) || runnerCandidates[0];
2490
2513
  const { execSync: execSync7 } = await import("child_process");
2491
2514
  if (!fs10.existsSync(path10.join(runnerDir, "BrowseRunner.xcodeproj", "project.pbxproj"))) {
2492
2515
  log2("Generating Xcode project...");
@@ -2661,158 +2684,282 @@ var init_sim_cli = __esm({
2661
2684
  }
2662
2685
  });
2663
2686
 
2687
+ // src/app/macos/bridge.ts
2688
+ var bridge_exports3 = {};
2689
+ __export(bridge_exports3, {
2690
+ createMacOSBridge: () => createMacOSBridge,
2691
+ ensureMacOSBridge: () => ensureMacOSBridge,
2692
+ resolveBridgePath: () => resolveBridgePath
2693
+ });
2694
+ function resolveBridgePath() {
2695
+ const candidates = [
2696
+ // 1. Local dev build
2697
+ path11.resolve(__dirname_bridge, "../../../browse-ax/.build/release/browse-ax"),
2698
+ path11.resolve(__dirname_bridge, "../../../browse-ax/.build/debug/browse-ax"),
2699
+ // 2. Installed alongside source (bin/ at project root)
2700
+ path11.resolve(__dirname_bridge, "../../bin/browse-ax"),
2701
+ // 3. Bundled build (dist/browse.cjs → ../bin/)
2702
+ path11.resolve(__dirname_bridge, "../bin/browse-ax")
2703
+ ];
2704
+ for (const p of candidates) {
2705
+ if (fs11.existsSync(p)) return p;
2706
+ }
2707
+ const lazyPath = path11.join(
2708
+ process.env.BROWSE_LOCAL_DIR || path11.join(process.cwd(), ".browse"),
2709
+ "bin",
2710
+ "browse-ax"
2711
+ );
2712
+ if (fs11.existsSync(lazyPath)) return lazyPath;
2713
+ throw new Error(
2714
+ "browse-ax binary not found. Run: browse enable macos\nOr build manually: cd browse-ax && swift build -c release"
2715
+ );
2716
+ }
2717
+ async function ensureMacOSBridge() {
2718
+ if (process.platform !== "darwin") {
2719
+ throw new Error("App automation requires macOS (uses Accessibility API)");
2720
+ }
2721
+ return resolveBridgePath();
2722
+ }
2723
+ async function execBridge(bridgePath, args) {
2724
+ return new Promise((resolve9, reject) => {
2725
+ const proc = (0, import_child_process8.spawn)(bridgePath, args, {
2726
+ stdio: ["ignore", "pipe", "pipe"]
2727
+ });
2728
+ const chunks = [];
2729
+ const errChunks = [];
2730
+ proc.stdout.on("data", (c) => chunks.push(c));
2731
+ proc.stderr.on("data", (c) => errChunks.push(c));
2732
+ proc.on("close", (code) => {
2733
+ const stdout = Buffer.concat(chunks).toString("utf-8").trim();
2734
+ const stderr = Buffer.concat(errChunks).toString("utf-8").trim();
2735
+ if (code !== 0) {
2736
+ try {
2737
+ const err = JSON.parse(stderr || stdout);
2738
+ reject(new Error(err.error || `Bridge exited with code ${code}`));
2739
+ } catch {
2740
+ reject(new Error(stderr || `Bridge exited with code ${code}`));
2741
+ }
2742
+ return;
2743
+ }
2744
+ try {
2745
+ resolve9(JSON.parse(stdout));
2746
+ } catch {
2747
+ reject(new Error(`Invalid bridge output: ${stdout.slice(0, 200)}`));
2748
+ }
2749
+ });
2750
+ });
2751
+ }
2752
+ function createMacOSBridge(bridgePath, pid) {
2753
+ const base = ["--pid", String(pid)];
2754
+ return {
2755
+ async tree() {
2756
+ return execBridge(bridgePath, [...base, "tree"]);
2757
+ },
2758
+ async action(nodePath, actionName) {
2759
+ return execBridge(bridgePath, [...base, "action", JSON.stringify(nodePath), actionName]);
2760
+ },
2761
+ async setValue(nodePath, value) {
2762
+ return execBridge(bridgePath, [...base, "set-value", JSON.stringify(nodePath), value]);
2763
+ },
2764
+ async type(text) {
2765
+ return execBridge(bridgePath, [...base, "type", text]);
2766
+ },
2767
+ async press(key) {
2768
+ return execBridge(bridgePath, [...base, "press", key]);
2769
+ },
2770
+ async screenshot(outputPath) {
2771
+ return execBridge(bridgePath, [...base, "screenshot", outputPath]);
2772
+ },
2773
+ async state() {
2774
+ return execBridge(bridgePath, [...base, "state"]);
2775
+ }
2776
+ };
2777
+ }
2778
+ var import_child_process8, path11, fs11, import_url5, __filename_bridge2, __dirname_bridge;
2779
+ var init_bridge3 = __esm({
2780
+ "src/app/macos/bridge.ts"() {
2781
+ "use strict";
2782
+ import_child_process8 = require("child_process");
2783
+ path11 = __toESM(require("path"), 1);
2784
+ fs11 = __toESM(require("fs"), 1);
2785
+ import_url5 = require("url");
2786
+ __filename_bridge2 = (0, import_url5.fileURLToPath)(__import_meta_url);
2787
+ __dirname_bridge = path11.dirname(__filename_bridge2);
2788
+ }
2789
+ });
2790
+
2664
2791
  // src/enable.ts
2665
2792
  var enable_exports = {};
2666
2793
  __export(enable_exports, {
2667
2794
  handleEnable: () => handleEnable
2668
2795
  });
2796
+ function findPath(candidates) {
2797
+ return candidates.find((p) => fs12.existsSync(p)) || null;
2798
+ }
2669
2799
  async function enableAndroid() {
2670
2800
  log("Enabling Android...");
2671
- const { ensureAndroidBridge: ensureAndroidBridge2, AdbNotFoundError: AdbNotFoundError2, installAdb: installAdb2 } = await Promise.resolve().then(() => (init_bridge(), bridge_exports));
2672
- try {
2673
- await ensureAndroidBridge2();
2674
- log("adb: already available");
2675
- } catch (err) {
2676
- if (err instanceof AdbNotFoundError2) {
2677
- const ok = await installAdb2(log);
2678
- if (!ok) throw new Error("Failed to install adb");
2679
- }
2801
+ const prebuiltApk = findPath([
2802
+ path12.resolve(__dirname_enable, "../bin/browse-android.apk"),
2803
+ path12.resolve(__dirname_enable, "../../bin/browse-android.apk"),
2804
+ path12.resolve(__dirname_enable, "browse-android.apk")
2805
+ ]);
2806
+ if (prebuiltApk) {
2807
+ log(`Driver APK: ${prebuiltApk}`);
2808
+ log("Android ready (pre-built).");
2809
+ return;
2680
2810
  }
2681
- const { ensureJavaHome: ensureJavaHome2, findSdkRoot: findSdkRoot2, installSdk: installSdk2, installSystemImage: installSystemImage2, createAvd: createAvd2 } = await Promise.resolve().then(() => (init_emulator(), emulator_exports));
2682
- ensureJavaHome2();
2811
+ log("Pre-built APK not found, building from source...");
2812
+ const { ensureJavaHome: ensureJavaHome2, findSdkRoot: findSdkRoot2, installSdk: installSdk2, createAvd: createAvd2 } = await Promise.resolve().then(() => (init_emulator(), emulator_exports));
2813
+ const { installAdb: installAdb2 } = await Promise.resolve().then(() => (init_bridge(), bridge_exports));
2683
2814
  try {
2684
- (0, import_child_process8.execSync)("java -version", { stdio: "ignore", timeout: 5e3 });
2685
- log("Java: available");
2815
+ (0, import_child_process9.execSync)("adb version", { stdio: "ignore", timeout: 5e3 });
2816
+ log("adb: available");
2686
2817
  } catch {
2687
- log("Java not found. Run: brew install openjdk@21");
2818
+ log("Installing adb...");
2819
+ await installAdb2(log);
2688
2820
  }
2821
+ ensureJavaHome2();
2822
+ const hasJava = () => {
2823
+ try {
2824
+ (0, import_child_process9.execSync)("java -version", { stdio: "ignore", timeout: 5e3 });
2825
+ return true;
2826
+ } catch {
2827
+ return false;
2828
+ }
2829
+ };
2830
+ if (!hasJava() && process.platform === "darwin") {
2831
+ log("Installing Java (JDK 21)...");
2832
+ try {
2833
+ (0, import_child_process9.execSync)("brew install openjdk@21", { stdio: ["ignore", "pipe", "pipe"], timeout: 3e5 });
2834
+ } catch {
2835
+ }
2836
+ ensureJavaHome2();
2837
+ }
2838
+ if (!hasJava()) throw new Error("Java not found. Install: brew install openjdk@21");
2839
+ log("Java: available");
2689
2840
  let sdkRoot = findSdkRoot2();
2690
- if (!sdkRoot) {
2691
- sdkRoot = await installSdk2(log);
2692
- }
2693
- if (sdkRoot) {
2694
- log(`SDK: ${sdkRoot}`);
2695
- const sdkMgr = [
2696
- path11.join(sdkRoot, "cmdline-tools/latest/bin/sdkmanager"),
2697
- path11.join(sdkRoot, "bin/sdkmanager")
2698
- ].find((p) => fs11.existsSync(p));
2699
- if (sdkMgr) {
2841
+ if (!sdkRoot) sdkRoot = await installSdk2(log);
2842
+ if (!sdkRoot) throw new Error("Android SDK not found.");
2843
+ log(`SDK: ${sdkRoot}`);
2844
+ const sdkMgr = findPath([
2845
+ path12.join(sdkRoot, "cmdline-tools/latest/bin/sdkmanager"),
2846
+ path12.join(sdkRoot, "bin/sdkmanager")
2847
+ ]);
2848
+ if (sdkMgr) {
2849
+ try {
2850
+ (0, import_child_process9.execSync)(`yes | "${sdkMgr}" --licenses`, { stdio: "ignore", timeout: 6e4, shell: "/bin/bash" });
2851
+ } catch {
2852
+ }
2853
+ for (const comp of ["emulator", "platform-tools", "platforms;android-35", "build-tools;35.0.0", "system-images;android-35;google_apis;arm64-v8a"]) {
2700
2854
  try {
2701
- (0, import_child_process8.execSync)(`yes | "${sdkMgr}" --licenses`, { stdio: "ignore", timeout: 6e4, shell: "/bin/bash" });
2855
+ (0, import_child_process9.execSync)(`"${sdkMgr}" --install "${comp}"`, { stdio: "ignore", timeout: 3e5 });
2702
2856
  } catch {
2703
2857
  }
2704
- const components = ["emulator", "platform-tools", "platforms;android-35", "build-tools;35.0.0", "system-images;android-35;google_apis;arm64-v8a"];
2705
- for (const comp of components) {
2706
- try {
2707
- (0, import_child_process8.execSync)(`"${sdkMgr}" --install "${comp}"`, { stdio: "ignore", timeout: 3e5 });
2708
- } catch {
2709
- }
2710
- }
2711
- log("SDK components: installed");
2712
- const avdMgr = [
2713
- path11.join(sdkRoot, "cmdline-tools/latest/bin/avdmanager"),
2714
- path11.join(sdkRoot, "bin/avdmanager")
2715
- ].find((p) => fs11.existsSync(p));
2716
- if (avdMgr) {
2717
- try {
2718
- const avds = (0, import_child_process8.execSync)(`"${avdMgr}" list avd -c`, { encoding: "utf-8", timeout: 1e4, stdio: ["ignore", "pipe", "pipe"] }).trim();
2719
- if (!avds.includes("browse_default")) {
2720
- createAvd2(sdkRoot, avdMgr, log);
2721
- } else {
2722
- log("AVD browse_default: exists");
2723
- }
2724
- } catch {
2725
- }
2858
+ }
2859
+ log("SDK components: installed");
2860
+ }
2861
+ const avdMgr = findPath([
2862
+ path12.join(sdkRoot, "cmdline-tools/latest/bin/avdmanager"),
2863
+ path12.join(sdkRoot, "bin/avdmanager")
2864
+ ]);
2865
+ if (avdMgr) {
2866
+ try {
2867
+ const avds = (0, import_child_process9.execSync)(`"${avdMgr}" list avd -c`, { encoding: "utf-8", timeout: 1e4, stdio: ["ignore", "pipe", "pipe"] }).trim();
2868
+ if (!avds.includes("browse_default")) {
2869
+ createAvd2(sdkRoot, avdMgr, log);
2870
+ } else {
2871
+ log("AVD: browse_default exists");
2726
2872
  }
2873
+ } catch {
2727
2874
  }
2728
2875
  }
2729
- const driverDir = path11.resolve(__dirname_enable, "../browse-android");
2730
- const apkPath = path11.join(driverDir, "app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk");
2731
- if (!fs11.existsSync(apkPath) && fs11.existsSync(path11.join(driverDir, "gradlew"))) {
2732
- const { findSdkRoot: findSdkRoot3, ensureJavaHome: ensureJavaHome3 } = await Promise.resolve().then(() => (init_emulator(), emulator_exports));
2733
- ensureJavaHome3();
2734
- const sdkRoot2 = findSdkRoot3();
2735
- if (sdkRoot2) process.env.ANDROID_HOME = sdkRoot2;
2876
+ const driverDir = findPath([
2877
+ path12.resolve(__dirname_enable, "../browse-android"),
2878
+ path12.resolve(__dirname_enable, "../../browse-android")
2879
+ ].filter((d) => fs12.existsSync(path12.join(d, "gradlew"))));
2880
+ if (driverDir) {
2881
+ if (!process.env.ANDROID_HOME) process.env.ANDROID_HOME = sdkRoot;
2736
2882
  log("Building Android driver APK...");
2737
2883
  try {
2738
- (0, import_child_process8.execSync)("./gradlew :app:assembleDebug :app:assembleDebugAndroidTest --no-daemon", {
2884
+ (0, import_child_process9.execSync)("./gradlew :app:assembleDebug :app:assembleDebugAndroidTest --no-daemon", {
2739
2885
  cwd: driverDir,
2740
2886
  stdio: ["ignore", "pipe", "pipe"],
2741
2887
  timeout: 3e5
2742
2888
  });
2743
- log("Android driver APK built.");
2889
+ log("APK built.");
2744
2890
  } catch (err) {
2745
2891
  log(`APK build failed: ${err.message?.split("\n")[0]}`);
2746
2892
  }
2747
- } else if (fs11.existsSync(apkPath)) {
2748
- log("Android driver APK: already built");
2749
2893
  }
2750
2894
  log("Android enabled.");
2751
2895
  }
2752
2896
  async function enableIOS() {
2753
2897
  log("Enabling iOS...");
2754
- if (process.platform !== "darwin") {
2755
- throw new Error("iOS requires macOS with Xcode installed.");
2756
- }
2898
+ if (process.platform !== "darwin") throw new Error("iOS requires macOS with Xcode.");
2757
2899
  try {
2758
- (0, import_child_process8.execSync)("xcodebuild -version", { stdio: "pipe", timeout: 1e4 });
2900
+ (0, import_child_process9.execSync)("xcodebuild -version", { stdio: "pipe", timeout: 1e4 });
2759
2901
  log("Xcode: available");
2760
2902
  } catch {
2761
- throw new Error("Xcode not found. Install from the App Store or: xcode-select --install");
2903
+ throw new Error("Xcode not found. Install from the App Store.");
2762
2904
  }
2763
2905
  try {
2764
- (0, import_child_process8.execSync)("which xcodegen", { stdio: "pipe", timeout: 5e3 });
2765
- log("xcodegen: available");
2906
+ (0, import_child_process9.execSync)("which xcodegen", { stdio: "pipe", timeout: 5e3 });
2766
2907
  } catch {
2767
2908
  log("Installing xcodegen...");
2768
2909
  try {
2769
- (0, import_child_process8.execSync)("brew install xcodegen", { stdio: ["ignore", "pipe", "pipe"], timeout: 12e4 });
2910
+ (0, import_child_process9.execSync)("brew install xcodegen", { stdio: ["ignore", "pipe", "pipe"], timeout: 12e4 });
2770
2911
  } catch {
2771
2912
  throw new Error("xcodegen not found. Install: brew install xcodegen");
2772
2913
  }
2773
2914
  }
2774
- const runnerDir = path11.resolve(__dirname_enable, "../browse-ios-runner");
2775
- if (fs11.existsSync(path11.join(runnerDir, "project.yml"))) {
2776
- if (!fs11.existsSync(path11.join(runnerDir, "BrowseRunner.xcodeproj", "project.pbxproj"))) {
2777
- log("Generating Xcode project...");
2778
- (0, import_child_process8.execSync)("xcodegen generate --spec project.yml", { cwd: runnerDir, stdio: "pipe" });
2779
- }
2780
- const { resolveSimulator: resolveSimulator2 } = await Promise.resolve().then(() => (init_controller(), controller_exports));
2781
- let udid;
2782
- try {
2783
- const sim = await resolveSimulator2();
2784
- udid = sim.udid;
2785
- } catch {
2786
- log("No iOS Simulator found. Skipping pre-build (will build on first sim start).");
2787
- log("iOS enabled (source ready).");
2788
- return;
2789
- }
2915
+ log("xcodegen: available");
2916
+ const runnerDir = findPath([
2917
+ path12.resolve(__dirname_enable, "../browse-ios-runner"),
2918
+ path12.resolve(__dirname_enable, "../../browse-ios-runner"),
2919
+ path12.resolve(__dirname_enable, "../bin/browse-ios-runner")
2920
+ ].filter((d) => fs12.existsSync(path12.join(d, "project.yml"))));
2921
+ if (!runnerDir) {
2922
+ throw new Error("iOS runner source not found. Reinstall: npm install -g @ulpi/browse");
2923
+ }
2924
+ if (!fs12.existsSync(path12.join(runnerDir, "BrowseRunner.xcodeproj", "project.pbxproj"))) {
2925
+ log("Generating Xcode project...");
2926
+ (0, import_child_process9.execSync)("xcodegen generate --spec project.yml", { cwd: runnerDir, stdio: "pipe" });
2927
+ }
2928
+ const { resolveSimulator: resolveSimulator2 } = await Promise.resolve().then(() => (init_controller(), controller_exports));
2929
+ try {
2930
+ const sim = await resolveSimulator2();
2790
2931
  log("Building iOS runner...");
2791
- (0, import_child_process8.execSync)(
2792
- `xcodebuild build-for-testing -project BrowseRunner.xcodeproj -scheme BrowseRunnerApp -sdk iphonesimulator -destination "id=${udid}" -derivedDataPath .build CODE_SIGN_IDENTITY="" CODE_SIGNING_ALLOWED=NO -quiet`,
2932
+ (0, import_child_process9.execSync)(
2933
+ `xcodebuild build-for-testing -project BrowseRunner.xcodeproj -scheme BrowseRunnerApp -sdk iphonesimulator -destination "id=${sim.udid}" -derivedDataPath .build CODE_SIGN_IDENTITY="" CODE_SIGNING_ALLOWED=NO -quiet`,
2793
2934
  { cwd: runnerDir, stdio: "pipe", timeout: 12e4 }
2794
2935
  );
2795
2936
  log("iOS runner built.");
2937
+ } catch {
2938
+ log("No simulator found. Runner will build on first sim start.");
2796
2939
  }
2797
2940
  log("iOS enabled.");
2798
2941
  }
2799
2942
  async function enableMacOS() {
2800
2943
  log("Enabling macOS...");
2801
- if (process.platform !== "darwin") {
2802
- throw new Error("macOS app automation requires macOS.");
2944
+ if (process.platform !== "darwin") throw new Error("macOS app automation requires macOS.");
2945
+ const { resolveBridgePath: resolveBridgePath2 } = await Promise.resolve().then(() => (init_bridge3(), bridge_exports3));
2946
+ try {
2947
+ const existing = resolveBridgePath2();
2948
+ log(`browse-ax: ${existing}`);
2949
+ log("macOS enabled.");
2950
+ return;
2951
+ } catch {
2803
2952
  }
2804
- const axDir = path11.resolve(__dirname_enable, "../browse-ax");
2805
- const axBin = path11.join(axDir, ".build/release/browse-ax");
2806
- if (fs11.existsSync(path11.join(axDir, "Package.swift"))) {
2807
- if (fs11.existsSync(axBin)) {
2808
- log("browse-ax: already built");
2809
- } else {
2810
- log("Building browse-ax...");
2811
- (0, import_child_process8.execSync)("swift build -c release", { cwd: axDir, stdio: ["ignore", "pipe", "pipe"], timeout: 12e4 });
2812
- log("browse-ax built.");
2813
- }
2953
+ const axDir = findPath([
2954
+ path12.resolve(__dirname_enable, "../browse-ax"),
2955
+ path12.resolve(__dirname_enable, "../../browse-ax")
2956
+ ].filter((d) => fs12.existsSync(path12.join(d, "Package.swift"))));
2957
+ if (axDir) {
2958
+ log("Building browse-ax...");
2959
+ (0, import_child_process9.execSync)("swift build -c release", { cwd: axDir, stdio: ["ignore", "pipe", "pipe"], timeout: 12e4 });
2960
+ log("browse-ax built.");
2814
2961
  } else {
2815
- throw new Error("browse-ax source not found.");
2962
+ throw new Error("browse-ax not found. Reinstall: npm install -g @ulpi/browse");
2816
2963
  }
2817
2964
  log("macOS enabled.");
2818
2965
  }
@@ -2821,8 +2968,8 @@ async function handleEnable(args) {
2821
2968
  if (!platform || platform === "--help") {
2822
2969
  console.log("Usage: browse enable android|ios|macos|all");
2823
2970
  console.log("");
2824
- console.log("Downloads dependencies and builds native drivers for each platform.");
2825
- console.log("Run once \u2014 everything is cached for future use.");
2971
+ console.log("Verifies native drivers are ready. Pre-built binaries ship with npm install.");
2972
+ console.log("If missing (building from source), installs dependencies and builds them.");
2826
2973
  return;
2827
2974
  }
2828
2975
  const platforms = platform === "all" ? ["android", "ios", "macos"] : [platform];
@@ -2849,15 +2996,15 @@ async function handleEnable(args) {
2849
2996
  console.log("");
2850
2997
  }
2851
2998
  }
2852
- var import_child_process8, fs11, path11, import_url5, __dirname_enable, log;
2999
+ var import_child_process9, fs12, path12, import_url6, __dirname_enable, log;
2853
3000
  var init_enable = __esm({
2854
3001
  "src/enable.ts"() {
2855
3002
  "use strict";
2856
- import_child_process8 = require("child_process");
2857
- fs11 = __toESM(require("fs"), 1);
2858
- path11 = __toESM(require("path"), 1);
2859
- import_url5 = require("url");
2860
- __dirname_enable = path11.dirname((0, import_url5.fileURLToPath)(__import_meta_url));
3003
+ import_child_process9 = require("child_process");
3004
+ fs12 = __toESM(require("fs"), 1);
3005
+ path12 = __toESM(require("path"), 1);
3006
+ import_url6 = require("url");
3007
+ __dirname_enable = path12.dirname((0, import_url6.fileURLToPath)(__import_meta_url));
2861
3008
  log = (msg) => process.stderr.write(`[browse] ${msg}
2862
3009
  `);
2863
3010
  }
@@ -3151,8 +3298,8 @@ async function handleReadCommand(command, args, bm, buffers) {
3151
3298
  case "eval": {
3152
3299
  const filePath = args[0];
3153
3300
  if (!filePath) throw new Error("Usage: browse eval <js-file>");
3154
- if (!fs12.existsSync(filePath)) throw new Error(`File not found: ${filePath}`);
3155
- const code = fs12.readFileSync(filePath, "utf-8");
3301
+ if (!fs13.existsSync(filePath)) throw new Error(`File not found: ${filePath}`);
3302
+ const code = fs13.readFileSync(filePath, "utf-8");
3156
3303
  const result = await evalCtx.evaluate(code);
3157
3304
  return typeof result === "object" ? JSON.stringify(result, null, 2) : String(result ?? "");
3158
3305
  }
@@ -3492,13 +3639,13 @@ function registerReadDefinitions(registry4) {
3492
3639
  });
3493
3640
  }
3494
3641
  }
3495
- var fs12;
3642
+ var fs13;
3496
3643
  var init_read = __esm({
3497
3644
  "src/commands/read.ts"() {
3498
3645
  "use strict";
3499
3646
  init_emulation();
3500
3647
  init_constants();
3501
- fs12 = __toESM(require("fs"), 1);
3648
+ fs13 = __toESM(require("fs"), 1);
3502
3649
  }
3503
3650
  });
3504
3651
 
@@ -4190,14 +4337,14 @@ async function handleWriteCommand(command, args, bm, domainFilter) {
4190
4337
  const file = args[1];
4191
4338
  if (!file) throw new Error("Usage: browse cookie export <file>");
4192
4339
  const cookies = await page.context().cookies();
4193
- fs13.writeFileSync(file, JSON.stringify(cookies, null, 2));
4340
+ fs14.writeFileSync(file, JSON.stringify(cookies, null, 2));
4194
4341
  return `Exported ${cookies.length} cookie(s) to ${file}`;
4195
4342
  }
4196
4343
  if (cookieStr === "import") {
4197
4344
  const file = args[1];
4198
4345
  if (!file) throw new Error("Usage: browse cookie import <file>");
4199
- if (!fs13.existsSync(file)) throw new Error(`File not found: ${file}`);
4200
- const cookies = JSON.parse(fs13.readFileSync(file, "utf-8"));
4346
+ if (!fs14.existsSync(file)) throw new Error(`File not found: ${file}`);
4347
+ const cookies = JSON.parse(fs14.readFileSync(file, "utf-8"));
4201
4348
  if (!Array.isArray(cookies)) throw new Error("Cookie file must contain a JSON array of cookie objects");
4202
4349
  await page.context().addCookies(cookies);
4203
4350
  return `Imported ${cookies.length} cookie(s) from ${file}`;
@@ -4264,7 +4411,7 @@ Note: Cookies and tab URLs preserved. localStorage/sessionStorage were reset (Pl
4264
4411
  const [selector, ...filePaths] = args;
4265
4412
  if (!selector || filePaths.length === 0) throw new Error("Usage: browse upload <selector> <file1> [file2] ...");
4266
4413
  for (const fp of filePaths) {
4267
- if (!fs13.existsSync(fp)) throw new Error(`File not found: ${fp}`);
4414
+ if (!fs14.existsSync(fp)) throw new Error(`File not found: ${fp}`);
4268
4415
  }
4269
4416
  const resolved = bm.resolveRef(selector);
4270
4417
  if ("locator" in resolved) {
@@ -4724,13 +4871,13 @@ function registerWriteDefinitions(registry4) {
4724
4871
  });
4725
4872
  }
4726
4873
  }
4727
- var fs13;
4874
+ var fs14;
4728
4875
  var init_write = __esm({
4729
4876
  "src/commands/write.ts"() {
4730
4877
  "use strict";
4731
4878
  init_emulation();
4732
4879
  init_constants();
4733
- fs13 = __toESM(require("fs"), 1);
4880
+ fs14 = __toESM(require("fs"), 1);
4734
4881
  }
4735
4882
  });
4736
4883
 
@@ -5104,13 +5251,13 @@ ${legend.join("\n")}`;
5104
5251
  const diffArgs = args.filter((a) => a !== "--full");
5105
5252
  const baseline = diffArgs[0];
5106
5253
  if (!baseline) throw new Error("Usage: browse screenshot-diff <baseline> [current] [--threshold 0.1] [--full]");
5107
- if (!fs14.existsSync(baseline)) throw new Error(`Baseline file not found: ${baseline}`);
5254
+ if (!fs15.existsSync(baseline)) throw new Error(`Baseline file not found: ${baseline}`);
5108
5255
  let thresholdPct = 0.1;
5109
5256
  const threshIdx = diffArgs.indexOf("--threshold");
5110
5257
  if (threshIdx !== -1 && diffArgs[threshIdx + 1]) {
5111
5258
  thresholdPct = parseFloat(diffArgs[threshIdx + 1]);
5112
5259
  }
5113
- const baselineBuffer = fs14.readFileSync(baseline);
5260
+ const baselineBuffer = fs15.readFileSync(baseline);
5114
5261
  let currentBuffer;
5115
5262
  let currentPath;
5116
5263
  for (let i = 1; i < diffArgs.length; i++) {
@@ -5124,8 +5271,8 @@ ${legend.join("\n")}`;
5124
5271
  }
5125
5272
  }
5126
5273
  if (currentPath) {
5127
- if (!fs14.existsSync(currentPath)) throw new Error(`Current screenshot not found: ${currentPath}`);
5128
- currentBuffer = fs14.readFileSync(currentPath);
5274
+ if (!fs15.existsSync(currentPath)) throw new Error(`Current screenshot not found: ${currentPath}`);
5275
+ currentBuffer = fs15.readFileSync(currentPath);
5129
5276
  } else {
5130
5277
  const page = bm.getPage();
5131
5278
  currentBuffer = await page.screenshot({ fullPage: isFullPageDiff });
@@ -5135,7 +5282,7 @@ ${legend.join("\n")}`;
5135
5282
  const extIdx = baseline.lastIndexOf(".");
5136
5283
  const diffPath = extIdx > 0 ? baseline.slice(0, extIdx) + "-diff" + baseline.slice(extIdx) : baseline + "-diff.png";
5137
5284
  if (!result.passed && result.diffImage) {
5138
- fs14.writeFileSync(diffPath, result.diffImage);
5285
+ fs15.writeFileSync(diffPath, result.diffImage);
5139
5286
  }
5140
5287
  return [
5141
5288
  `Pixels: ${result.totalPixels}`,
@@ -5150,11 +5297,11 @@ ${legend.join("\n")}`;
5150
5297
  throw new Error(`Unknown screenshots command: ${command}`);
5151
5298
  }
5152
5299
  }
5153
- var fs14, LOCAL_DIR;
5300
+ var fs15, LOCAL_DIR;
5154
5301
  var init_screenshots = __esm({
5155
5302
  "src/commands/meta/screenshots.ts"() {
5156
5303
  "use strict";
5157
- fs14 = __toESM(require("fs"), 1);
5304
+ fs15 = __toESM(require("fs"), 1);
5158
5305
  LOCAL_DIR = process.env.BROWSE_LOCAL_DIR || "/tmp";
5159
5306
  }
5160
5307
  });
@@ -12920,7 +13067,7 @@ async function handleRecordingCommand(command, args, target, currentSession) {
12920
13067
  throw new Error(`Unknown format: ${format}. Use "browse" (chain JSON), "replay" (Puppeteer), "playwright" (Playwright Test), or "flow" (YAML).`);
12921
13068
  }
12922
13069
  if (filePath) {
12923
- fs15.writeFileSync(filePath, output);
13070
+ fs16.writeFileSync(filePath, output);
12924
13071
  return `Exported ${steps.length} steps as ${format}: ${filePath}`;
12925
13072
  }
12926
13073
  return output;
@@ -12945,7 +13092,7 @@ async function handleRecordingCommand(command, args, target, currentSession) {
12945
13092
  const { formatAsHar: formatAsHar2 } = await Promise.resolve().then(() => (init_har(), har_exports));
12946
13093
  const har = formatAsHar2(sessionBuffers.networkBuffer, recording.startTime);
12947
13094
  const harPath = args[1] || (currentSession ? `${currentSession.outputDir}/recording.har` : `${LOCAL_DIR2}/browse-recording.har`);
12948
- fs15.writeFileSync(harPath, JSON.stringify(har, null, 2));
13095
+ fs16.writeFileSync(harPath, JSON.stringify(har, null, 2));
12949
13096
  const entryCount = har.log.entries.length;
12950
13097
  return `HAR saved: ${harPath} (${entryCount} entries)`;
12951
13098
  }
@@ -12981,11 +13128,11 @@ async function handleRecordingCommand(command, args, target, currentSession) {
12981
13128
  throw new Error(`Unknown recording command: ${command}`);
12982
13129
  }
12983
13130
  }
12984
- var fs15, LOCAL_DIR2;
13131
+ var fs16, LOCAL_DIR2;
12985
13132
  var init_recording = __esm({
12986
13133
  "src/commands/meta/recording.ts"() {
12987
13134
  "use strict";
12988
- fs15 = __toESM(require("fs"), 1);
13135
+ fs16 = __toESM(require("fs"), 1);
12989
13136
  LOCAL_DIR2 = process.env.BROWSE_LOCAL_DIR || "/tmp";
12990
13137
  }
12991
13138
  });
@@ -13009,20 +13156,20 @@ async function saveSessionState(sessionDir, context, encryptionKey) {
13009
13156
  } else {
13010
13157
  content = json;
13011
13158
  }
13012
- fs16.mkdirSync(sessionDir, { recursive: true });
13013
- fs16.writeFileSync(path12.join(sessionDir, STATE_FILENAME), content, { mode: 384 });
13159
+ fs17.mkdirSync(sessionDir, { recursive: true });
13160
+ fs17.writeFileSync(path13.join(sessionDir, STATE_FILENAME), content, { mode: 384 });
13014
13161
  } catch (err) {
13015
13162
  console.log(`[session-persist] Warning: failed to save state: ${err.message}`);
13016
13163
  }
13017
13164
  }
13018
13165
  async function loadSessionState(sessionDir, context, encryptionKey) {
13019
- const statePath = path12.join(sessionDir, STATE_FILENAME);
13020
- if (!fs16.existsSync(statePath)) {
13166
+ const statePath = path13.join(sessionDir, STATE_FILENAME);
13167
+ if (!fs17.existsSync(statePath)) {
13021
13168
  return false;
13022
13169
  }
13023
13170
  let stateData;
13024
13171
  try {
13025
- const raw = fs16.readFileSync(statePath, "utf-8");
13172
+ const raw = fs17.readFileSync(statePath, "utf-8");
13026
13173
  const parsed = JSON.parse(raw);
13027
13174
  if (parsed.encrypted) {
13028
13175
  if (!encryptionKey) {
@@ -13078,24 +13225,24 @@ async function loadSessionState(sessionDir, context, encryptionKey) {
13078
13225
  }
13079
13226
  }
13080
13227
  function hasPersistedState(sessionDir) {
13081
- return fs16.existsSync(path12.join(sessionDir, STATE_FILENAME));
13228
+ return fs17.existsSync(path13.join(sessionDir, STATE_FILENAME));
13082
13229
  }
13083
13230
  function cleanOldStates(localDir, maxAgeDays) {
13084
13231
  const maxAgeMs = maxAgeDays * 24 * 60 * 60 * 1e3;
13085
13232
  const now = Date.now();
13086
13233
  let deleted = 0;
13087
- const statesDir = path12.join(localDir, "states");
13088
- if (fs16.existsSync(statesDir)) {
13234
+ const statesDir = path13.join(localDir, "states");
13235
+ if (fs17.existsSync(statesDir)) {
13089
13236
  try {
13090
- const entries = fs16.readdirSync(statesDir);
13237
+ const entries = fs17.readdirSync(statesDir);
13091
13238
  for (const entry of entries) {
13092
13239
  if (!entry.endsWith(".json")) continue;
13093
- const filePath = path12.join(statesDir, entry);
13240
+ const filePath = path13.join(statesDir, entry);
13094
13241
  try {
13095
- const stat = fs16.statSync(filePath);
13242
+ const stat = fs17.statSync(filePath);
13096
13243
  if (!stat.isFile()) continue;
13097
13244
  if (now - stat.mtimeMs > maxAgeMs) {
13098
- fs16.unlinkSync(filePath);
13245
+ fs17.unlinkSync(filePath);
13099
13246
  deleted++;
13100
13247
  }
13101
13248
  } catch (_) {
@@ -13104,23 +13251,23 @@ function cleanOldStates(localDir, maxAgeDays) {
13104
13251
  } catch (_) {
13105
13252
  }
13106
13253
  }
13107
- const sessionsDir = path12.join(localDir, "sessions");
13108
- if (fs16.existsSync(sessionsDir)) {
13254
+ const sessionsDir = path13.join(localDir, "sessions");
13255
+ if (fs17.existsSync(sessionsDir)) {
13109
13256
  try {
13110
- const sessionDirs = fs16.readdirSync(sessionsDir);
13257
+ const sessionDirs = fs17.readdirSync(sessionsDir);
13111
13258
  for (const dir of sessionDirs) {
13112
- const dirPath = path12.join(sessionsDir, dir);
13259
+ const dirPath = path13.join(sessionsDir, dir);
13113
13260
  try {
13114
- const dirStat = fs16.statSync(dirPath);
13261
+ const dirStat = fs17.statSync(dirPath);
13115
13262
  if (!dirStat.isDirectory()) continue;
13116
13263
  } catch (_) {
13117
13264
  continue;
13118
13265
  }
13119
- const statePath = path12.join(dirPath, STATE_FILENAME);
13266
+ const statePath = path13.join(dirPath, STATE_FILENAME);
13120
13267
  try {
13121
- const stat = fs16.statSync(statePath);
13268
+ const stat = fs17.statSync(statePath);
13122
13269
  if (now - stat.mtimeMs > maxAgeMs) {
13123
- fs16.unlinkSync(statePath);
13270
+ fs17.unlinkSync(statePath);
13124
13271
  deleted++;
13125
13272
  }
13126
13273
  } catch (_) {
@@ -13131,12 +13278,12 @@ function cleanOldStates(localDir, maxAgeDays) {
13131
13278
  }
13132
13279
  return { deleted };
13133
13280
  }
13134
- var fs16, path12, STATE_FILENAME;
13281
+ var fs17, path13, STATE_FILENAME;
13135
13282
  var init_persist = __esm({
13136
13283
  "src/session/persist.ts"() {
13137
13284
  "use strict";
13138
- fs16 = __toESM(require("fs"), 1);
13139
- path12 = __toESM(require("path"), 1);
13285
+ fs17 = __toESM(require("fs"), 1);
13286
+ path13 = __toESM(require("path"), 1);
13140
13287
  init_encryption();
13141
13288
  STATE_FILENAME = "state.json";
13142
13289
  }
@@ -13571,7 +13718,7 @@ async function handleSessionsCommand(command, args, bm, sessionManager2, current
13571
13718
  const lines = entries.map(
13572
13719
  (e) => `[${new Date(e.timestamp).toISOString()}] [${e.level}] ${e.text}`
13573
13720
  ).join("\n") + "\n";
13574
- fs17.appendFileSync(consolePath, lines);
13721
+ fs18.appendFileSync(consolePath, lines);
13575
13722
  buffers.lastConsoleFlushed = buffers.consoleTotalAdded;
13576
13723
  }
13577
13724
  const newNetworkCount = buffers.networkTotalAdded - buffers.lastNetworkFlushed;
@@ -13581,7 +13728,7 @@ async function handleSessionsCommand(command, args, bm, sessionManager2, current
13581
13728
  const lines = entries.map(
13582
13729
  (e) => `[${new Date(e.timestamp).toISOString()}] ${e.method} ${e.url} \u2192 ${e.status || "pending"} (${e.duration || "?"}ms, ${e.size || "?"}B)`
13583
13730
  ).join("\n") + "\n";
13584
- fs17.appendFileSync(networkPath, lines);
13731
+ fs18.appendFileSync(networkPath, lines);
13585
13732
  buffers.lastNetworkFlushed = buffers.networkTotalAdded;
13586
13733
  }
13587
13734
  }
@@ -13597,22 +13744,22 @@ async function handleSessionsCommand(command, args, bm, sessionManager2, current
13597
13744
  const statesDir = `${LOCAL_DIR3}/states`;
13598
13745
  const statePath = `${statesDir}/${name}.json`;
13599
13746
  if (subcommand === "list") {
13600
- if (!fs17.existsSync(statesDir)) return "(no saved states)";
13601
- const files = fs17.readdirSync(statesDir).filter((f) => f.endsWith(".json"));
13747
+ if (!fs18.existsSync(statesDir)) return "(no saved states)";
13748
+ const files = fs18.readdirSync(statesDir).filter((f) => f.endsWith(".json"));
13602
13749
  if (files.length === 0) return "(no saved states)";
13603
13750
  const lines = [];
13604
13751
  for (const file of files) {
13605
13752
  const fp = `${statesDir}/${file}`;
13606
- const stat = fs17.statSync(fp);
13753
+ const stat = fs18.statSync(fp);
13607
13754
  lines.push(` ${file.replace(".json", "")} ${stat.size}B ${new Date(stat.mtimeMs).toISOString()}`);
13608
13755
  }
13609
13756
  return lines.join("\n");
13610
13757
  }
13611
13758
  if (subcommand === "show") {
13612
- if (!fs17.existsSync(statePath)) {
13759
+ if (!fs18.existsSync(statePath)) {
13613
13760
  throw new Error(`State file not found: ${statePath}`);
13614
13761
  }
13615
- const data = JSON.parse(fs17.readFileSync(statePath, "utf-8"));
13762
+ const data = JSON.parse(fs18.readFileSync(statePath, "utf-8"));
13616
13763
  const cookieCount = data.cookies?.length || 0;
13617
13764
  const originCount = data.origins?.length || 0;
13618
13765
  const storageItems = (data.origins || []).reduce((sum, o) => sum + (o.localStorage?.length || 0), 0);
@@ -13637,15 +13784,15 @@ async function handleSessionsCommand(command, args, bm, sessionManager2, current
13637
13784
  const context = bm.getContext();
13638
13785
  if (!context) throw new Error("No browser context");
13639
13786
  const state = await context.storageState();
13640
- fs17.mkdirSync(statesDir, { recursive: true });
13641
- fs17.writeFileSync(statePath, JSON.stringify(state, null, 2), { mode: 384 });
13787
+ fs18.mkdirSync(statesDir, { recursive: true });
13788
+ fs18.writeFileSync(statePath, JSON.stringify(state, null, 2), { mode: 384 });
13642
13789
  return `State saved: ${statePath}`;
13643
13790
  }
13644
13791
  if (subcommand === "load") {
13645
- if (!fs17.existsSync(statePath)) {
13792
+ if (!fs18.existsSync(statePath)) {
13646
13793
  throw new Error(`State file not found: ${statePath}. Run "browse state save ${name}" first.`);
13647
13794
  }
13648
- const stateData = JSON.parse(fs17.readFileSync(statePath, "utf-8"));
13795
+ const stateData = JSON.parse(fs18.readFileSync(statePath, "utf-8"));
13649
13796
  const context = bm.getContext();
13650
13797
  if (!context) throw new Error("No browser context");
13651
13798
  const warnings = [];
@@ -13698,12 +13845,12 @@ ${snapshot}`;
13698
13845
  throw new Error(`Unknown sessions command: ${command}`);
13699
13846
  }
13700
13847
  }
13701
- var fs17, LOCAL_DIR3;
13848
+ var fs18, LOCAL_DIR3;
13702
13849
  var init_sessions = __esm({
13703
13850
  "src/commands/meta/sessions.ts"() {
13704
13851
  "use strict";
13705
13852
  init_sanitize();
13706
- fs17 = __toESM(require("fs"), 1);
13853
+ fs18 = __toESM(require("fs"), 1);
13707
13854
  LOCAL_DIR3 = process.env.BROWSE_LOCAL_DIR || "/tmp";
13708
13855
  }
13709
13856
  });
@@ -14106,7 +14253,7 @@ var init_lib = __esm({
14106
14253
  tokenize: function tokenize(value) {
14107
14254
  return Array.from(value);
14108
14255
  },
14109
- join: function join13(chars) {
14256
+ join: function join14(chars) {
14110
14257
  return chars.join("");
14111
14258
  },
14112
14259
  postProcess: function postProcess(changeObjects) {
@@ -19667,19 +19814,19 @@ __export(detection_exports, {
19667
19814
  detectStack: () => detectStack
19668
19815
  });
19669
19816
  function loadCustomSignatures(localDir) {
19670
- const detectionDir = path13.join(localDir, "detections");
19671
- if (!fs18.existsSync(detectionDir)) return [];
19817
+ const detectionDir = path14.join(localDir, "detections");
19818
+ if (!fs19.existsSync(detectionDir)) return [];
19672
19819
  const sigs = [];
19673
19820
  let entries;
19674
19821
  try {
19675
- entries = fs18.readdirSync(detectionDir).filter((f) => f.endsWith(".json"));
19822
+ entries = fs19.readdirSync(detectionDir).filter((f) => f.endsWith(".json"));
19676
19823
  } catch {
19677
19824
  return [];
19678
19825
  }
19679
19826
  for (const entry of entries) {
19680
- const filePath = path13.join(detectionDir, entry);
19827
+ const filePath = path14.join(detectionDir, entry);
19681
19828
  try {
19682
- const raw = fs18.readFileSync(filePath, "utf-8");
19829
+ const raw = fs19.readFileSync(filePath, "utf-8");
19683
19830
  const parsed = JSON.parse(raw);
19684
19831
  if (parsed == null || typeof parsed !== "object" || parsed.version !== 1 || typeof parsed.name !== "string" || typeof parsed.detect !== "string") {
19685
19832
  process.stderr.write(
@@ -19805,15 +19952,15 @@ async function detectStack(page, networkEntries, localDir) {
19805
19952
  const thirdParty = buildThirdPartyInventory(page, networkEntries ?? []);
19806
19953
  return { frameworks, saas, infrastructure, thirdParty, custom };
19807
19954
  }
19808
- var fs18, path13, THIRD_PARTY_DOMAINS;
19955
+ var fs19, path14, THIRD_PARTY_DOMAINS;
19809
19956
  var init_detection = __esm({
19810
19957
  "src/detection/index.ts"() {
19811
19958
  "use strict";
19812
19959
  init_frameworks();
19813
19960
  init_saas();
19814
19961
  init_infrastructure();
19815
- fs18 = __toESM(require("fs"), 1);
19816
- path13 = __toESM(require("path"), 1);
19962
+ fs19 = __toESM(require("fs"), 1);
19963
+ path14 = __toESM(require("path"), 1);
19817
19964
  THIRD_PARTY_DOMAINS = {
19818
19965
  // Analytics
19819
19966
  "google-analytics.com": "analytics",
@@ -21895,35 +22042,35 @@ __export(persist_exports2, {
21895
22042
  saveAudit: () => saveAudit
21896
22043
  });
21897
22044
  function auditsDir(localDir) {
21898
- return path14.join(localDir, "audits");
22045
+ return path15.join(localDir, "audits");
21899
22046
  }
21900
22047
  function auditPath(localDir, name) {
21901
- return path14.join(auditsDir(localDir), `${sanitizeName(name)}.json`);
22048
+ return path15.join(auditsDir(localDir), `${sanitizeName(name)}.json`);
21902
22049
  }
21903
22050
  function saveAudit(localDir, name, report) {
21904
22051
  const dir = auditsDir(localDir);
21905
- fs19.mkdirSync(dir, { recursive: true });
22052
+ fs20.mkdirSync(dir, { recursive: true });
21906
22053
  const filePath = auditPath(localDir, name);
21907
- fs19.writeFileSync(filePath, JSON.stringify(report, null, 2), { mode: 384 });
22054
+ fs20.writeFileSync(filePath, JSON.stringify(report, null, 2), { mode: 384 });
21908
22055
  return filePath;
21909
22056
  }
21910
22057
  function loadAudit(localDir, name) {
21911
22058
  const filePath = auditPath(localDir, name);
21912
- if (!fs19.existsSync(filePath)) {
22059
+ if (!fs20.existsSync(filePath)) {
21913
22060
  throw new Error(
21914
22061
  `Audit not found: ${filePath}. Run "browse perf-audit save ${name}" first.`
21915
22062
  );
21916
22063
  }
21917
- return JSON.parse(fs19.readFileSync(filePath, "utf-8"));
22064
+ return JSON.parse(fs20.readFileSync(filePath, "utf-8"));
21918
22065
  }
21919
22066
  function listAudits(localDir) {
21920
22067
  const dir = auditsDir(localDir);
21921
- if (!fs19.existsSync(dir)) return [];
21922
- const files = fs19.readdirSync(dir).filter((f) => f.endsWith(".json"));
22068
+ if (!fs20.existsSync(dir)) return [];
22069
+ const files = fs20.readdirSync(dir).filter((f) => f.endsWith(".json"));
21923
22070
  if (files.length === 0) return [];
21924
22071
  const entries = files.map((file) => {
21925
- const fp = path14.join(dir, file);
21926
- const stat = fs19.statSync(fp);
22072
+ const fp = path15.join(dir, file);
22073
+ const stat = fs20.statSync(fp);
21927
22074
  return {
21928
22075
  name: file.replace(".json", ""),
21929
22076
  sizeBytes: stat.size,
@@ -21935,19 +22082,19 @@ function listAudits(localDir) {
21935
22082
  }
21936
22083
  function deleteAudit(localDir, name) {
21937
22084
  const filePath = auditPath(localDir, name);
21938
- if (!fs19.existsSync(filePath)) {
22085
+ if (!fs20.existsSync(filePath)) {
21939
22086
  throw new Error(
21940
22087
  `Audit not found: ${filePath}. Nothing to delete.`
21941
22088
  );
21942
22089
  }
21943
- fs19.unlinkSync(filePath);
22090
+ fs20.unlinkSync(filePath);
21944
22091
  }
21945
- var fs19, path14;
22092
+ var fs20, path15;
21946
22093
  var init_persist2 = __esm({
21947
22094
  "src/perf-audit/persist.ts"() {
21948
22095
  "use strict";
21949
- fs19 = __toESM(require("fs"), 1);
21950
- path14 = __toESM(require("path"), 1);
22096
+ fs20 = __toESM(require("fs"), 1);
22097
+ path15 = __toESM(require("path"), 1);
21951
22098
  init_sanitize();
21952
22099
  }
21953
22100
  });
@@ -23394,12 +23541,12 @@ async function autoDetectSelector(page, field) {
23394
23541
  }
23395
23542
  throw new Error("Could not auto-detect submit button.");
23396
23543
  }
23397
- var fs20, path15, AuthVault;
23544
+ var fs21, path16, AuthVault;
23398
23545
  var init_auth_vault = __esm({
23399
23546
  "src/security/auth-vault.ts"() {
23400
23547
  "use strict";
23401
- fs20 = __toESM(require("fs"), 1);
23402
- path15 = __toESM(require("path"), 1);
23548
+ fs21 = __toESM(require("fs"), 1);
23549
+ path16 = __toESM(require("path"), 1);
23403
23550
  init_constants();
23404
23551
  init_encryption();
23405
23552
  init_sanitize();
@@ -23407,11 +23554,11 @@ var init_auth_vault = __esm({
23407
23554
  authDir;
23408
23555
  encryptionKey;
23409
23556
  constructor(localDir) {
23410
- this.authDir = path15.join(localDir, "auth");
23557
+ this.authDir = path16.join(localDir, "auth");
23411
23558
  this.encryptionKey = resolveEncryptionKey(localDir);
23412
23559
  }
23413
23560
  save(name, url, username, password, selectors) {
23414
- fs20.mkdirSync(this.authDir, { recursive: true });
23561
+ fs21.mkdirSync(this.authDir, { recursive: true });
23415
23562
  const { ciphertext, iv, authTag } = encrypt(password, this.encryptionKey);
23416
23563
  const now = (/* @__PURE__ */ new Date()).toISOString();
23417
23564
  const credential = {
@@ -23428,15 +23575,15 @@ var init_auth_vault = __esm({
23428
23575
  createdAt: now,
23429
23576
  updatedAt: now
23430
23577
  };
23431
- const filePath = path15.join(this.authDir, `${sanitizeName(name)}.json`);
23432
- fs20.writeFileSync(filePath, JSON.stringify(credential, null, 2), { mode: 384 });
23578
+ const filePath = path16.join(this.authDir, `${sanitizeName(name)}.json`);
23579
+ fs21.writeFileSync(filePath, JSON.stringify(credential, null, 2), { mode: 384 });
23433
23580
  }
23434
23581
  load(name) {
23435
- const filePath = path15.join(this.authDir, `${sanitizeName(name)}.json`);
23436
- if (!fs20.existsSync(filePath)) {
23582
+ const filePath = path16.join(this.authDir, `${sanitizeName(name)}.json`);
23583
+ if (!fs21.existsSync(filePath)) {
23437
23584
  throw new Error(`Credential "${name}" not found. Run "browse auth list" to see saved credentials.`);
23438
23585
  }
23439
- return JSON.parse(fs20.readFileSync(filePath, "utf-8"));
23586
+ return JSON.parse(fs21.readFileSync(filePath, "utf-8"));
23440
23587
  }
23441
23588
  async login(name, bm) {
23442
23589
  const cred = this.load(name);
@@ -23457,11 +23604,11 @@ var init_auth_vault = __esm({
23457
23604
  return `Logged in as ${cred.username} at ${page.url()}`;
23458
23605
  }
23459
23606
  list() {
23460
- if (!fs20.existsSync(this.authDir)) return [];
23461
- const files = fs20.readdirSync(this.authDir).filter((f) => f.endsWith(".json"));
23607
+ if (!fs21.existsSync(this.authDir)) return [];
23608
+ const files = fs21.readdirSync(this.authDir).filter((f) => f.endsWith(".json"));
23462
23609
  return files.map((f) => {
23463
23610
  try {
23464
- const data = JSON.parse(fs20.readFileSync(path15.join(this.authDir, f), "utf-8"));
23611
+ const data = JSON.parse(fs21.readFileSync(path16.join(this.authDir, f), "utf-8"));
23465
23612
  return {
23466
23613
  name: data.name,
23467
23614
  url: data.url,
@@ -23475,11 +23622,11 @@ var init_auth_vault = __esm({
23475
23622
  }).filter(Boolean);
23476
23623
  }
23477
23624
  delete(name) {
23478
- const filePath = path15.join(this.authDir, `${sanitizeName(name)}.json`);
23479
- if (!fs20.existsSync(filePath)) {
23625
+ const filePath = path16.join(this.authDir, `${sanitizeName(name)}.json`);
23626
+ if (!fs21.existsSync(filePath)) {
23480
23627
  throw new Error(`Credential "${name}" not found.`);
23481
23628
  }
23482
- fs20.unlinkSync(filePath);
23629
+ fs21.unlinkSync(filePath);
23483
23630
  }
23484
23631
  };
23485
23632
  }
@@ -23616,20 +23763,20 @@ __export(policy_exports, {
23616
23763
  function findFileUpward(filename) {
23617
23764
  let dir = process.cwd();
23618
23765
  for (let i = 0; i < 20; i++) {
23619
- const candidate = path16.join(dir, filename);
23620
- if (fs21.existsSync(candidate)) return candidate;
23621
- const parent = path16.dirname(dir);
23766
+ const candidate = path17.join(dir, filename);
23767
+ if (fs22.existsSync(candidate)) return candidate;
23768
+ const parent = path17.dirname(dir);
23622
23769
  if (parent === dir) break;
23623
23770
  dir = parent;
23624
23771
  }
23625
23772
  return null;
23626
23773
  }
23627
- var fs21, path16, PolicyChecker;
23774
+ var fs22, path17, PolicyChecker;
23628
23775
  var init_policy = __esm({
23629
23776
  "src/security/policy.ts"() {
23630
23777
  "use strict";
23631
- fs21 = __toESM(require("fs"), 1);
23632
- path16 = __toESM(require("path"), 1);
23778
+ fs22 = __toESM(require("fs"), 1);
23779
+ path17 = __toESM(require("path"), 1);
23633
23780
  PolicyChecker = class {
23634
23781
  filePath = null;
23635
23782
  lastMtime = 0;
@@ -23648,10 +23795,10 @@ var init_policy = __esm({
23648
23795
  reload() {
23649
23796
  if (!this.filePath) return;
23650
23797
  try {
23651
- const stat = fs21.statSync(this.filePath);
23798
+ const stat = fs22.statSync(this.filePath);
23652
23799
  if (stat.mtimeMs === this.lastMtime) return;
23653
23800
  this.lastMtime = stat.mtimeMs;
23654
- const raw = fs21.readFileSync(this.filePath, "utf-8");
23801
+ const raw = fs22.readFileSync(this.filePath, "utf-8");
23655
23802
  this.policy = JSON.parse(raw);
23656
23803
  } catch {
23657
23804
  }
@@ -23828,104 +23975,6 @@ var init_buffers = __esm({
23828
23975
  }
23829
23976
  });
23830
23977
 
23831
- // src/app/macos/bridge.ts
23832
- var bridge_exports3 = {};
23833
- __export(bridge_exports3, {
23834
- createMacOSBridge: () => createMacOSBridge,
23835
- ensureMacOSBridge: () => ensureMacOSBridge,
23836
- resolveBridgePath: () => resolveBridgePath
23837
- });
23838
- function resolveBridgePath() {
23839
- const localBuild = path17.resolve(__dirname_bridge, "../../../browse-ax/.build/release/browse-ax");
23840
- if (fs22.existsSync(localBuild)) return localBuild;
23841
- const localDebug = path17.resolve(__dirname_bridge, "../../../browse-ax/.build/debug/browse-ax");
23842
- if (fs22.existsSync(localDebug)) return localDebug;
23843
- const installed = path17.resolve(__dirname_bridge, "../../bin/browse-ax");
23844
- if (fs22.existsSync(installed)) return installed;
23845
- const lazyPath = path17.join(
23846
- process.env.BROWSE_LOCAL_DIR || path17.join(process.cwd(), ".browse"),
23847
- "bin",
23848
- "browse-ax"
23849
- );
23850
- if (fs22.existsSync(lazyPath)) return lazyPath;
23851
- throw new Error(
23852
- "browse-ax binary not found. Build it with: cd browse-ax && swift build -c release\nOr run: browse doctor --platform macos"
23853
- );
23854
- }
23855
- async function ensureMacOSBridge() {
23856
- if (process.platform !== "darwin") {
23857
- throw new Error("App automation requires macOS (uses Accessibility API)");
23858
- }
23859
- return resolveBridgePath();
23860
- }
23861
- async function execBridge(bridgePath, args) {
23862
- return new Promise((resolve9, reject) => {
23863
- const proc = (0, import_child_process9.spawn)(bridgePath, args, {
23864
- stdio: ["ignore", "pipe", "pipe"]
23865
- });
23866
- const chunks = [];
23867
- const errChunks = [];
23868
- proc.stdout.on("data", (c) => chunks.push(c));
23869
- proc.stderr.on("data", (c) => errChunks.push(c));
23870
- proc.on("close", (code) => {
23871
- const stdout = Buffer.concat(chunks).toString("utf-8").trim();
23872
- const stderr = Buffer.concat(errChunks).toString("utf-8").trim();
23873
- if (code !== 0) {
23874
- try {
23875
- const err = JSON.parse(stderr || stdout);
23876
- reject(new Error(err.error || `Bridge exited with code ${code}`));
23877
- } catch {
23878
- reject(new Error(stderr || `Bridge exited with code ${code}`));
23879
- }
23880
- return;
23881
- }
23882
- try {
23883
- resolve9(JSON.parse(stdout));
23884
- } catch {
23885
- reject(new Error(`Invalid bridge output: ${stdout.slice(0, 200)}`));
23886
- }
23887
- });
23888
- });
23889
- }
23890
- function createMacOSBridge(bridgePath, pid) {
23891
- const base = ["--pid", String(pid)];
23892
- return {
23893
- async tree() {
23894
- return execBridge(bridgePath, [...base, "tree"]);
23895
- },
23896
- async action(nodePath, actionName) {
23897
- return execBridge(bridgePath, [...base, "action", JSON.stringify(nodePath), actionName]);
23898
- },
23899
- async setValue(nodePath, value) {
23900
- return execBridge(bridgePath, [...base, "set-value", JSON.stringify(nodePath), value]);
23901
- },
23902
- async type(text) {
23903
- return execBridge(bridgePath, [...base, "type", text]);
23904
- },
23905
- async press(key) {
23906
- return execBridge(bridgePath, [...base, "press", key]);
23907
- },
23908
- async screenshot(outputPath) {
23909
- return execBridge(bridgePath, [...base, "screenshot", outputPath]);
23910
- },
23911
- async state() {
23912
- return execBridge(bridgePath, [...base, "state"]);
23913
- }
23914
- };
23915
- }
23916
- var import_child_process9, path17, fs22, import_url6, __filename_bridge2, __dirname_bridge;
23917
- var init_bridge3 = __esm({
23918
- "src/app/macos/bridge.ts"() {
23919
- "use strict";
23920
- import_child_process9 = require("child_process");
23921
- path17 = __toESM(require("path"), 1);
23922
- fs22 = __toESM(require("fs"), 1);
23923
- import_url6 = require("url");
23924
- __filename_bridge2 = (0, import_url6.fileURLToPath)(__import_meta_url);
23925
- __dirname_bridge = path17.dirname(__filename_bridge2);
23926
- }
23927
- });
23928
-
23929
23978
  // src/commands/meta/system.ts
23930
23979
  async function handleSystemCommand(command, args, target, shutdown2, sessionManager2, currentSession, lifecycle) {
23931
23980
  switch (command) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ulpi/browse",
3
- "version": "2.3.1",
3
+ "version": "2.3.3",
4
4
  "homepage": "https://browse.ulpi.io",
5
5
  "repository": {
6
6
  "type": "git",