@ulpi/browse 2.3.2 → 2.3.4

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/browse.cjs CHANGED
@@ -281,7 +281,7 @@ async function getKeychainPassword(service) {
281
281
  stderr += chunk;
282
282
  });
283
283
  const exitPromise = new Promise(
284
- (resolve9) => proc.on("close", (code) => resolve9(code))
284
+ (resolve10) => proc.on("close", (code) => resolve10(code))
285
285
  );
286
286
  const timeout = new Promise(
287
287
  (_, reject) => setTimeout(() => {
@@ -511,11 +511,11 @@ function getChromeUserDataDir() {
511
511
  return null;
512
512
  }
513
513
  function isPortFree(port) {
514
- return new Promise((resolve9) => {
514
+ return new Promise((resolve10) => {
515
515
  const srv = net.createServer();
516
- srv.once("error", () => resolve9(false));
516
+ srv.once("error", () => resolve10(false));
517
517
  srv.once("listening", () => {
518
- srv.close(() => resolve9(true));
518
+ srv.close(() => resolve10(true));
519
519
  });
520
520
  srv.listen(port, "127.0.0.1");
521
521
  });
@@ -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.2",
867
+ version: "2.3.4",
868
868
  homepage: "https://browse.ulpi.io",
869
869
  repository: {
870
870
  type: "git",
@@ -949,6 +949,19 @@ var install_skill_exports = {};
949
949
  __export(install_skill_exports, {
950
950
  installSkill: () => installSkill
951
951
  });
952
+ function copySkillDir(srcDir, destDir, projectRoot) {
953
+ fs6.mkdirSync(destDir, { recursive: true });
954
+ for (const entry of fs6.readdirSync(srcDir, { withFileTypes: true })) {
955
+ const srcPath = path6.join(srcDir, entry.name);
956
+ const destPath = path6.join(destDir, entry.name);
957
+ if (entry.isDirectory()) {
958
+ copySkillDir(srcPath, destPath, projectRoot);
959
+ } else if (entry.name.endsWith(".md")) {
960
+ fs6.copyFileSync(srcPath, destPath);
961
+ console.log(` ${path6.relative(projectRoot, destPath)}`);
962
+ }
963
+ }
964
+ }
952
965
  function installSkill(targetDir) {
953
966
  const dir = targetDir || process.cwd();
954
967
  const hasGit = fs6.existsSync(path6.join(dir, ".git"));
@@ -958,32 +971,23 @@ function installSkill(targetDir) {
958
971
  console.error("Run from a directory with .git or .claude, or pass the path as an argument.");
959
972
  process.exit(1);
960
973
  }
961
- const skillDir = path6.join(dir, ".claude", "skills", "browse");
962
- fs6.mkdirSync(skillDir, { recursive: true });
963
- const skillSourceDir = path6.resolve(path6.dirname((0, import_url.fileURLToPath)(__import_meta_url)), "..", "skill");
964
- if (!fs6.existsSync(skillSourceDir)) {
965
- console.error(`Skill directory not found at ${skillSourceDir}`);
974
+ const skillSourceRoot = path6.resolve(path6.dirname((0, import_url.fileURLToPath)(__import_meta_url)), "..", "skill");
975
+ if (!fs6.existsSync(skillSourceRoot)) {
976
+ console.error(`Skill directory not found at ${skillSourceRoot}`);
966
977
  console.error("Is @ulpi/browse installed correctly?");
967
978
  process.exit(1);
968
979
  }
969
- const mdFiles = fs6.readdirSync(skillSourceDir).filter((f) => f.endsWith(".md"));
970
- if (mdFiles.length === 0) {
971
- console.error(`No .md files found in ${skillSourceDir}`);
980
+ const skillFolders = fs6.readdirSync(skillSourceRoot).filter(
981
+ (f) => fs6.statSync(path6.join(skillSourceRoot, f)).isDirectory()
982
+ );
983
+ if (skillFolders.length === 0) {
984
+ console.error(`No skill folders found in ${skillSourceRoot}`);
972
985
  process.exit(1);
973
986
  }
974
- for (const file of mdFiles) {
975
- fs6.copyFileSync(path6.join(skillSourceDir, file), path6.join(skillDir, file));
976
- console.log(`Skill installed: ${path6.relative(dir, path6.join(skillDir, file))}`);
977
- }
978
- const refsSourceDir = path6.join(skillSourceDir, "references");
979
- if (fs6.existsSync(refsSourceDir)) {
980
- const refsDestDir = path6.join(skillDir, "references");
981
- fs6.mkdirSync(refsDestDir, { recursive: true });
982
- const refFiles = fs6.readdirSync(refsSourceDir).filter((f) => f.endsWith(".md"));
983
- for (const file of refFiles) {
984
- fs6.copyFileSync(path6.join(refsSourceDir, file), path6.join(refsDestDir, file));
985
- console.log(`Skill installed: ${path6.relative(dir, path6.join(refsDestDir, file))}`);
986
- }
987
+ for (const folder of skillFolders) {
988
+ const srcDir = path6.join(skillSourceRoot, folder);
989
+ const destDir = path6.join(dir, ".claude", "skills", folder);
990
+ copySkillDir(srcDir, destDir, dir);
987
991
  }
988
992
  const settingsPath = path6.join(dir, ".claude", "settings.json");
989
993
  let settings = {};
@@ -1080,6 +1084,356 @@ var init_install_skill = __esm({
1080
1084
  }
1081
1085
  });
1082
1086
 
1087
+ // src/app/ios/controller.ts
1088
+ var controller_exports = {};
1089
+ __export(controller_exports, {
1090
+ addMedia: () => addMedia,
1091
+ bootSimulator: () => bootSimulator,
1092
+ checkXcodeTools: () => checkXcodeTools,
1093
+ clearStatusBar: () => clearStatusBar,
1094
+ getAppContainer: () => getAppContainer,
1095
+ grantPermission: () => grantPermission,
1096
+ installApp: () => installApp,
1097
+ isAppInstalled: () => isAppInstalled,
1098
+ launchApp: () => launchApp,
1099
+ listSimulators: () => listSimulators,
1100
+ openURL: () => openURL,
1101
+ resetPermissions: () => resetPermissions,
1102
+ resolveSimulator: () => resolveSimulator,
1103
+ revokePermission: () => revokePermission,
1104
+ screenshotSimulator: () => screenshotSimulator,
1105
+ setStatusBar: () => setStatusBar,
1106
+ shutdownSimulator: () => shutdownSimulator,
1107
+ terminateApp: () => terminateApp,
1108
+ uninstallApp: () => uninstallApp
1109
+ });
1110
+ async function simctl(...args) {
1111
+ try {
1112
+ const { stdout } = await execFileAsync("xcrun", ["simctl", ...args], {
1113
+ timeout: 6e4,
1114
+ maxBuffer: 10 * 1024 * 1024
1115
+ // 10 MB for device list JSON
1116
+ });
1117
+ return stdout.trim();
1118
+ } catch (err) {
1119
+ const stderr = err.stderr?.trim() || "";
1120
+ const message = stderr || err.message || "Unknown simctl error";
1121
+ throw new Error(`simctl ${args[0]} failed: ${message}`);
1122
+ }
1123
+ }
1124
+ async function listSimulators(filter) {
1125
+ const raw = await simctl("list", "devices", "--json");
1126
+ const parsed = JSON.parse(raw);
1127
+ const results = [];
1128
+ for (const [runtime, devices] of Object.entries(parsed.devices)) {
1129
+ for (const device of devices) {
1130
+ if (!device.isAvailable) continue;
1131
+ const info = {
1132
+ udid: device.udid,
1133
+ name: device.name,
1134
+ state: device.state,
1135
+ runtime,
1136
+ isAvailable: device.isAvailable
1137
+ };
1138
+ if (!filter || info.name.toLowerCase().includes(filter.toLowerCase()) || info.runtime.toLowerCase().includes(filter.toLowerCase())) {
1139
+ results.push(info);
1140
+ }
1141
+ }
1142
+ }
1143
+ return results;
1144
+ }
1145
+ async function resolveSimulator(identifier) {
1146
+ const all = await listSimulators();
1147
+ if (identifier) {
1148
+ const byUdid = all.find((s) => s.udid === identifier);
1149
+ if (byUdid) return byUdid;
1150
+ const byName = all.filter((s) => s.name === identifier && s.isAvailable);
1151
+ if (byName.length === 1) return byName[0];
1152
+ if (byName.length > 1) {
1153
+ const booted2 = byName.find((s) => s.state === "Booted");
1154
+ if (booted2) return booted2;
1155
+ return byName[0];
1156
+ }
1157
+ const fuzzy = all.filter((s) => s.name.includes(identifier) && s.isAvailable);
1158
+ if (fuzzy.length === 1) return fuzzy[0];
1159
+ if (fuzzy.length > 1) return fuzzy.find((s) => s.state === "Booted") || fuzzy[0];
1160
+ throw new Error(`Simulator '${identifier}' not found. Run: xcrun simctl list devices available`);
1161
+ }
1162
+ const booted = all.find((s) => s.state === "Booted");
1163
+ if (booted) return booted;
1164
+ if (all.length === 0) {
1165
+ throw new Error(
1166
+ 'No iOS Simulators available. Create one with:\n xcrun simctl create "iPhone 16 Pro" "com.apple.CoreSimulator.SimDeviceType.iPhone-16-Pro"'
1167
+ );
1168
+ }
1169
+ return all[0];
1170
+ }
1171
+ async function bootSimulator(udid) {
1172
+ const sims = await listSimulators();
1173
+ const sim = sims.find((s) => s.udid === udid);
1174
+ if (sim?.state === "Booted") return;
1175
+ await simctl("boot", udid);
1176
+ const deadline = Date.now() + 3e4;
1177
+ while (Date.now() < deadline) {
1178
+ const current = await listSimulators();
1179
+ const updated = current.find((s) => s.udid === udid);
1180
+ if (updated?.state === "Booted") return;
1181
+ await new Promise((r2) => setTimeout(r2, 1e3));
1182
+ }
1183
+ throw new Error(`Simulator ${udid} did not boot within 30 seconds`);
1184
+ }
1185
+ async function shutdownSimulator(udid) {
1186
+ await simctl("shutdown", udid);
1187
+ }
1188
+ async function installApp(udid, appPath) {
1189
+ await simctl("install", udid, appPath);
1190
+ }
1191
+ async function uninstallApp(udid, bundleId) {
1192
+ await simctl("uninstall", udid, bundleId);
1193
+ }
1194
+ async function launchApp(udid, bundleId, env) {
1195
+ const args = ["launch", udid, bundleId];
1196
+ if (env && Object.keys(env).length > 0) {
1197
+ for (const [key, value] of Object.entries(env)) {
1198
+ args.push(`SIMCTL_CHILD_${key}=${value}`);
1199
+ }
1200
+ }
1201
+ await simctl(...args);
1202
+ }
1203
+ async function terminateApp(udid, bundleId) {
1204
+ try {
1205
+ await simctl("terminate", udid, bundleId);
1206
+ } catch {
1207
+ }
1208
+ }
1209
+ async function isAppInstalled(udid, bundleId) {
1210
+ try {
1211
+ await simctl("get_app_container", udid, bundleId);
1212
+ return true;
1213
+ } catch {
1214
+ return false;
1215
+ }
1216
+ }
1217
+ async function grantPermission(udid, bundleId, permission) {
1218
+ await simctl("privacy", udid, "grant", permission, bundleId);
1219
+ }
1220
+ async function revokePermission(udid, bundleId, permission) {
1221
+ await simctl("privacy", udid, "revoke", permission, bundleId);
1222
+ }
1223
+ async function resetPermissions(udid, bundleId) {
1224
+ await simctl("privacy", udid, "reset", "all", bundleId);
1225
+ }
1226
+ async function screenshotSimulator(udid, outputPath) {
1227
+ await simctl("io", udid, "screenshot", outputPath);
1228
+ }
1229
+ async function getAppContainer(udid, bundleId, container = "data") {
1230
+ return simctl("get_app_container", udid, bundleId, container);
1231
+ }
1232
+ async function openURL(udid, url) {
1233
+ await simctl("openurl", udid, url);
1234
+ }
1235
+ async function addMedia(udid, ...paths) {
1236
+ await simctl("addmedia", udid, ...paths);
1237
+ }
1238
+ async function setStatusBar(udid, overrides) {
1239
+ const args = ["status_bar", udid, "override"];
1240
+ if (overrides.time) args.push("--time", overrides.time);
1241
+ if (overrides.batteryLevel !== void 0) args.push("--batteryLevel", String(overrides.batteryLevel));
1242
+ if (overrides.batteryState) args.push("--batteryState", overrides.batteryState);
1243
+ if (overrides.cellularBars !== void 0) args.push("--cellularBars", String(overrides.cellularBars));
1244
+ if (overrides.wifiBars !== void 0) args.push("--wifiBars", String(overrides.wifiBars));
1245
+ if (overrides.operatorName) args.push("--operatorName", overrides.operatorName);
1246
+ await simctl(...args);
1247
+ }
1248
+ async function clearStatusBar(udid) {
1249
+ await simctl("status_bar", udid, "clear");
1250
+ }
1251
+ async function checkXcodeTools() {
1252
+ try {
1253
+ await execFileAsync("xcrun", ["--version"], { timeout: 5e3 });
1254
+ } catch {
1255
+ throw new Error(
1256
+ "Xcode Command Line Tools not found.\nInstall with: xcode-select --install\nOr install Xcode from the Mac App Store."
1257
+ );
1258
+ }
1259
+ try {
1260
+ await execFileAsync("xcrun", ["simctl", "help"], { timeout: 5e3 });
1261
+ } catch {
1262
+ throw new Error(
1263
+ "simctl not available. Ensure Xcode is installed and selected:\n sudo xcode-select -s /Applications/Xcode.app"
1264
+ );
1265
+ }
1266
+ }
1267
+ var import_child_process3, import_util, execFileAsync;
1268
+ var init_controller = __esm({
1269
+ "src/app/ios/controller.ts"() {
1270
+ "use strict";
1271
+ import_child_process3 = require("child_process");
1272
+ import_util = require("util");
1273
+ execFileAsync = (0, import_util.promisify)(import_child_process3.execFile);
1274
+ }
1275
+ });
1276
+
1277
+ // src/app/resolve-app.ts
1278
+ var resolve_app_exports = {};
1279
+ __export(resolve_app_exports, {
1280
+ isAppFilePath: () => isAppFilePath,
1281
+ resolveAndroidApp: () => resolveAndroidApp,
1282
+ resolveIOSApp: () => resolveIOSApp
1283
+ });
1284
+ function isAppFilePath(value) {
1285
+ const lower = value.toLowerCase();
1286
+ if (lower.endsWith(".ipa") || lower.endsWith(".apk")) return true;
1287
+ if (lower.endsWith(".app") && fs7.existsSync(value)) return true;
1288
+ return false;
1289
+ }
1290
+ async function resolveIOSApp(appArg, udid, log2) {
1291
+ if (!isAppFilePath(appArg)) return appArg;
1292
+ const absPath = path7.resolve(appArg);
1293
+ if (appArg.toLowerCase().endsWith(".ipa")) {
1294
+ return resolveIPA(absPath, udid, log2);
1295
+ }
1296
+ if (!fs7.existsSync(absPath)) {
1297
+ throw new Error(`App bundle not found: ${absPath}`);
1298
+ }
1299
+ const bundleId = extractBundleId(absPath);
1300
+ log2(`Installing ${path7.basename(absPath)} (${bundleId})...`);
1301
+ const { installApp: installApp2 } = await Promise.resolve().then(() => (init_controller(), controller_exports));
1302
+ await installApp2(udid, absPath);
1303
+ return bundleId;
1304
+ }
1305
+ function extractBundleId(appPath) {
1306
+ const plistPath = path7.join(appPath, "Info.plist");
1307
+ if (!fs7.existsSync(plistPath)) {
1308
+ throw new Error(`Info.plist not found in ${appPath}. Is this a valid iOS app bundle?`);
1309
+ }
1310
+ try {
1311
+ const json = (0, import_child_process4.execSync)(`plutil -convert json -o - "${plistPath}"`, {
1312
+ encoding: "utf-8",
1313
+ timeout: 5e3
1314
+ });
1315
+ const parsed = JSON.parse(json);
1316
+ const bundleId = parsed.CFBundleIdentifier;
1317
+ if (!bundleId) {
1318
+ throw new Error(`CFBundleIdentifier not found in ${plistPath}`);
1319
+ }
1320
+ return bundleId;
1321
+ } catch (err) {
1322
+ if (err.message?.includes("CFBundleIdentifier")) throw err;
1323
+ throw new Error(`Failed to read bundle ID from ${plistPath}: ${err.message}`);
1324
+ }
1325
+ }
1326
+ async function resolveIPA(ipaPath, udid, log2) {
1327
+ if (!fs7.existsSync(ipaPath)) {
1328
+ throw new Error(`IPA file not found: ${ipaPath}`);
1329
+ }
1330
+ const tmpDir = fs7.mkdtempSync(path7.join(os3.tmpdir(), "browse-ipa-"));
1331
+ try {
1332
+ (0, import_child_process4.execSync)(`unzip -o -q "${ipaPath}" -d "${tmpDir}"`, { timeout: 3e4 });
1333
+ const payloadDir = path7.join(tmpDir, "Payload");
1334
+ if (!fs7.existsSync(payloadDir)) {
1335
+ throw new Error(`No Payload directory in ${ipaPath}. Is this a valid .ipa file?`);
1336
+ }
1337
+ const appDirs = fs7.readdirSync(payloadDir).filter((f) => f.endsWith(".app"));
1338
+ if (appDirs.length === 0) {
1339
+ throw new Error(`No .app bundle found in ${ipaPath}/Payload/`);
1340
+ }
1341
+ const appPath = path7.join(payloadDir, appDirs[0]);
1342
+ const bundleId = extractBundleId(appPath);
1343
+ log2(`Installing ${appDirs[0]} (${bundleId}) from IPA...`);
1344
+ const { installApp: installApp2 } = await Promise.resolve().then(() => (init_controller(), controller_exports));
1345
+ await installApp2(udid, appPath);
1346
+ return bundleId;
1347
+ } finally {
1348
+ try {
1349
+ fs7.rmSync(tmpDir, { recursive: true, force: true });
1350
+ } catch {
1351
+ }
1352
+ }
1353
+ }
1354
+ async function resolveAndroidApp(appArg, serial, log2) {
1355
+ if (!isAppFilePath(appArg)) return appArg;
1356
+ const absPath = path7.resolve(appArg);
1357
+ if (!fs7.existsSync(absPath)) {
1358
+ throw new Error(`APK file not found: ${absPath}`);
1359
+ }
1360
+ const packageName = extractPackageName(absPath);
1361
+ log2(`Installing ${path7.basename(absPath)} (${packageName})...`);
1362
+ try {
1363
+ (0, import_child_process4.execSync)(`adb -s ${serial} install -r -t "${absPath}"`, {
1364
+ stdio: "pipe",
1365
+ timeout: 6e4
1366
+ });
1367
+ } catch (err) {
1368
+ throw new Error(`Failed to install APK: ${err.message?.split("\n")[0]}`);
1369
+ }
1370
+ return packageName;
1371
+ }
1372
+ function extractPackageName(apkPath) {
1373
+ const aaptBins = findAaptBinaries();
1374
+ for (const aapt of aaptBins) {
1375
+ try {
1376
+ const output = (0, import_child_process4.execSync)(`"${aapt}" dump badging "${apkPath}"`, {
1377
+ encoding: "utf-8",
1378
+ timeout: 1e4,
1379
+ stdio: ["ignore", "pipe", "pipe"]
1380
+ });
1381
+ const match = output.match(/package:\s*name='([^']+)'/);
1382
+ if (match) return match[1];
1383
+ } catch {
1384
+ continue;
1385
+ }
1386
+ }
1387
+ throw new Error(
1388
+ `Cannot determine package name from ${path7.basename(apkPath)}.
1389
+ Install Android build-tools (browse enable android) or provide the package name directly:
1390
+ browse sim start --platform android --app com.example.myapp`
1391
+ );
1392
+ }
1393
+ function findAaptBinaries() {
1394
+ const bins = [];
1395
+ try {
1396
+ (0, import_child_process4.execSync)("which aapt2", { stdio: "pipe", timeout: 3e3 });
1397
+ bins.push("aapt2");
1398
+ } catch {
1399
+ }
1400
+ try {
1401
+ (0, import_child_process4.execSync)("which aapt", { stdio: "pipe", timeout: 3e3 });
1402
+ bins.push("aapt");
1403
+ } catch {
1404
+ }
1405
+ const sdkRoots = [
1406
+ process.env.ANDROID_HOME,
1407
+ process.env.ANDROID_SDK_ROOT,
1408
+ path7.join(os3.homedir(), "Library/Android/sdk"),
1409
+ "/opt/homebrew/share/android-commandlinetools",
1410
+ path7.join(os3.homedir(), "Android/Sdk")
1411
+ ].filter(Boolean);
1412
+ for (const sdk of sdkRoots) {
1413
+ const btDir = path7.join(sdk, "build-tools");
1414
+ if (!fs7.existsSync(btDir)) continue;
1415
+ const versions = fs7.readdirSync(btDir).sort().reverse();
1416
+ for (const ver of versions) {
1417
+ const aapt2 = path7.join(btDir, ver, "aapt2");
1418
+ const aapt = path7.join(btDir, ver, "aapt");
1419
+ if (fs7.existsSync(aapt2)) bins.push(aapt2);
1420
+ if (fs7.existsSync(aapt)) bins.push(aapt);
1421
+ break;
1422
+ }
1423
+ }
1424
+ return bins;
1425
+ }
1426
+ var import_child_process4, fs7, path7, os3;
1427
+ var init_resolve_app = __esm({
1428
+ "src/app/resolve-app.ts"() {
1429
+ "use strict";
1430
+ import_child_process4 = require("child_process");
1431
+ fs7 = __toESM(require("fs"), 1);
1432
+ path7 = __toESM(require("path"), 1);
1433
+ os3 = __toESM(require("os"), 1);
1434
+ }
1435
+ });
1436
+
1083
1437
  // src/app/android/bridge.ts
1084
1438
  var bridge_exports = {};
1085
1439
  __export(bridge_exports, {
@@ -1091,41 +1445,41 @@ __export(bridge_exports, {
1091
1445
  function resolveDriverApkPath() {
1092
1446
  const candidates = [
1093
1447
  // 1. Local dev build
1094
- path7.resolve(__dirname2, "../../../browse-android/app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk"),
1448
+ path8.resolve(__dirname2, "../../../browse-android/app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk"),
1095
1449
  // 2. Installed alongside source (bin/ at project root)
1096
- path7.resolve(__dirname2, "../../bin/browse-android.apk"),
1450
+ path8.resolve(__dirname2, "../../bin/browse-android.apk"),
1097
1451
  // 3. Bundled build (dist/browse.cjs → ../bin/)
1098
- path7.resolve(__dirname2, "../bin/browse-android.apk"),
1452
+ path8.resolve(__dirname2, "../bin/browse-android.apk"),
1099
1453
  // 4. Same directory as binary
1100
- path7.resolve(__dirname2, "browse-android.apk")
1454
+ path8.resolve(__dirname2, "browse-android.apk")
1101
1455
  ];
1102
1456
  for (const p of candidates) {
1103
- if (fs7.existsSync(p)) return p;
1457
+ if (fs8.existsSync(p)) return p;
1104
1458
  }
1105
- const lazyPath = path7.join(
1106
- process.env.BROWSE_LOCAL_DIR || path7.join(process.cwd(), ".browse"),
1459
+ const lazyPath = path8.join(
1460
+ process.env.BROWSE_LOCAL_DIR || path8.join(process.cwd(), ".browse"),
1107
1461
  "bin",
1108
1462
  "browse-android.apk"
1109
1463
  );
1110
- if (fs7.existsSync(lazyPath)) return lazyPath;
1464
+ if (fs8.existsSync(lazyPath)) return lazyPath;
1111
1465
  throw new Error(
1112
1466
  "browse-android APK not found. Run: browse enable android\nOr build manually: cd browse-android && ./gradlew :app:assembleDebugAndroidTest"
1113
1467
  );
1114
1468
  }
1115
1469
  function resolveAppApkPath() {
1116
1470
  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")
1471
+ path8.resolve(__dirname2, "../../../browse-android/app/build/outputs/apk/debug/app-debug.apk"),
1472
+ path8.resolve(__dirname2, "../../bin/browse-android-app.apk"),
1473
+ path8.resolve(__dirname2, "../bin/browse-android-app.apk"),
1474
+ path8.resolve(__dirname2, "browse-android-app.apk")
1121
1475
  ];
1122
1476
  for (const p of candidates) {
1123
- if (fs7.existsSync(p)) return p;
1477
+ if (fs8.existsSync(p)) return p;
1124
1478
  }
1125
1479
  return null;
1126
1480
  }
1127
1481
  function adbExec(serial, ...args) {
1128
- return (0, import_child_process3.execSync)(["adb", "-s", serial, ...args].join(" "), {
1482
+ return (0, import_child_process5.execSync)(["adb", "-s", serial, ...args].join(" "), {
1129
1483
  encoding: "utf-8",
1130
1484
  timeout: 3e4,
1131
1485
  stdio: ["ignore", "pipe", "pipe"]
@@ -1140,7 +1494,7 @@ function adbExecSafe(serial, ...args) {
1140
1494
  }
1141
1495
  async function ensureAndroidBridge(serial) {
1142
1496
  try {
1143
- (0, import_child_process3.execSync)("adb version", { stdio: "ignore", timeout: 5e3 });
1497
+ (0, import_child_process5.execSync)("adb version", { stdio: "ignore", timeout: 5e3 });
1144
1498
  } catch {
1145
1499
  throw new AdbNotFoundError();
1146
1500
  }
@@ -1151,7 +1505,7 @@ async function installAdb(log2) {
1151
1505
  `));
1152
1506
  if (process.platform === "darwin") {
1153
1507
  try {
1154
- (0, import_child_process3.execSync)("brew --version", { stdio: "ignore", timeout: 5e3 });
1508
+ (0, import_child_process5.execSync)("brew --version", { stdio: "ignore", timeout: 5e3 });
1155
1509
  } catch {
1156
1510
  print("Homebrew not found. Install it first: https://brew.sh");
1157
1511
  print("Then run: brew install android-platform-tools");
@@ -1159,11 +1513,11 @@ async function installAdb(log2) {
1159
1513
  }
1160
1514
  print("Installing Android platform-tools via Homebrew...");
1161
1515
  try {
1162
- (0, import_child_process3.execSync)("brew install android-platform-tools", {
1516
+ (0, import_child_process5.execSync)("brew install android-platform-tools", {
1163
1517
  stdio: ["ignore", "pipe", "pipe"],
1164
1518
  timeout: 12e4
1165
1519
  });
1166
- (0, import_child_process3.execSync)("adb version", { stdio: "ignore", timeout: 5e3 });
1520
+ (0, import_child_process5.execSync)("adb version", { stdio: "ignore", timeout: 5e3 });
1167
1521
  print("adb installed successfully.");
1168
1522
  return true;
1169
1523
  } catch (err) {
@@ -1178,7 +1532,7 @@ async function installAdb(log2) {
1178
1532
  return false;
1179
1533
  }
1180
1534
  function resolveDevice(serial) {
1181
- const output = (0, import_child_process3.execSync)("adb devices", { encoding: "utf-8", timeout: 5e3 });
1535
+ const output = (0, import_child_process5.execSync)("adb devices", { encoding: "utf-8", timeout: 5e3 });
1182
1536
  const lines = output.split("\n").slice(1).map((l) => l.trim()).filter((l) => l.length > 0 && !l.startsWith("*"));
1183
1537
  const booted = lines.filter((l) => l.endsWith(" device")).map((l) => l.split(" ")[0].trim());
1184
1538
  if (booted.length === 0) {
@@ -1245,7 +1599,7 @@ Install it first, e.g.: adb -s ${serial} install path/to/app.apk`
1245
1599
  }
1246
1600
  function installDriverApk(serial, apkPath) {
1247
1601
  try {
1248
- const stat = fs7.statSync(apkPath);
1602
+ const stat = fs8.statSync(apkPath);
1249
1603
  if (stat.size < 1024) {
1250
1604
  throw new Error(`APK file appears corrupt (${stat.size} bytes): ${apkPath}`);
1251
1605
  }
@@ -1261,7 +1615,7 @@ function installDriverApk(serial, apkPath) {
1261
1615
  return;
1262
1616
  }
1263
1617
  const appApkPath = resolveAppApkPath() || apkPath.replace("-androidTest", "");
1264
- if (fs7.existsSync(appApkPath)) {
1618
+ if (fs8.existsSync(appApkPath)) {
1265
1619
  try {
1266
1620
  adbExec(serial, "install", "-t", "-r", `"${appApkPath}"`);
1267
1621
  } catch {
@@ -1284,7 +1638,7 @@ function killStaleInstrumentation(serial) {
1284
1638
  adbExecSafe(serial, "shell", "am", "force-stop", DRIVER_PACKAGE);
1285
1639
  }
1286
1640
  function startInstrumentation(serial, targetPackage) {
1287
- const proc = (0, import_child_process3.spawn)(
1641
+ const proc = (0, import_child_process5.spawn)(
1288
1642
  "adb",
1289
1643
  [
1290
1644
  "-s",
@@ -1396,16 +1750,16 @@ function buildProtocol(serial, port) {
1396
1750
  }
1397
1751
  };
1398
1752
  }
1399
- var import_child_process3, fs7, path7, import_url2, __filename_bridge, __dirname2, DRIVER_PACKAGE, DRIVER_TEST_PACKAGE, DRIVER_RUNNER, DRIVER_PORT, DRIVER_HEALTH_TIMEOUT_MS, DRIVER_HEALTH_POLL_MS, INSTRUMENTATION_MAX_RETRIES, AdbNotFoundError;
1753
+ var import_child_process5, fs8, path8, import_url2, __filename_bridge, __dirname2, DRIVER_PACKAGE, DRIVER_TEST_PACKAGE, DRIVER_RUNNER, DRIVER_PORT, DRIVER_HEALTH_TIMEOUT_MS, DRIVER_HEALTH_POLL_MS, INSTRUMENTATION_MAX_RETRIES, AdbNotFoundError;
1400
1754
  var init_bridge = __esm({
1401
1755
  "src/app/android/bridge.ts"() {
1402
1756
  "use strict";
1403
- import_child_process3 = require("child_process");
1404
- fs7 = __toESM(require("fs"), 1);
1405
- path7 = __toESM(require("path"), 1);
1757
+ import_child_process5 = require("child_process");
1758
+ fs8 = __toESM(require("fs"), 1);
1759
+ path8 = __toESM(require("path"), 1);
1406
1760
  import_url2 = require("url");
1407
1761
  __filename_bridge = (0, import_url2.fileURLToPath)(__import_meta_url);
1408
- __dirname2 = path7.dirname(__filename_bridge);
1762
+ __dirname2 = path8.dirname(__filename_bridge);
1409
1763
  DRIVER_PACKAGE = "io.ulpi.browse.driver";
1410
1764
  DRIVER_TEST_PACKAGE = `${DRIVER_PACKAGE}.test`;
1411
1765
  DRIVER_RUNNER = "androidx.test.runner.AndroidJUnitRunner";
@@ -1445,7 +1799,7 @@ function ensureJavaHome() {
1445
1799
  "/usr/local/opt/openjdk/libexec/openjdk.jdk/Contents/Home"
1446
1800
  ];
1447
1801
  for (const jdk of candidates) {
1448
- if (fs8.existsSync(jdk)) {
1802
+ if (fs9.existsSync(jdk)) {
1449
1803
  process.env.JAVA_HOME = jdk;
1450
1804
  process.env.PATH = `${jdk}/bin:${process.env.PATH}`;
1451
1805
  return;
@@ -1458,20 +1812,20 @@ function sdkCandidates() {
1458
1812
  return [
1459
1813
  process.env.ANDROID_HOME || "",
1460
1814
  process.env.ANDROID_SDK_ROOT || "",
1461
- path8.join(home, "Library/Android/sdk"),
1815
+ path9.join(home, "Library/Android/sdk"),
1462
1816
  "/opt/homebrew/share/android-commandlinetools"
1463
1817
  ].filter(Boolean);
1464
1818
  }
1465
1819
  return [
1466
1820
  process.env.ANDROID_HOME || "",
1467
1821
  process.env.ANDROID_SDK_ROOT || "",
1468
- path8.join(home, "Android/Sdk"),
1822
+ path9.join(home, "Android/Sdk"),
1469
1823
  "/usr/lib/android-sdk"
1470
1824
  ].filter(Boolean);
1471
1825
  }
1472
1826
  function findSdkRoot() {
1473
1827
  for (const dir of sdkCandidates()) {
1474
- if (fs8.existsSync(path8.join(dir, "cmdline-tools")) || fs8.existsSync(path8.join(dir, "platform-tools")) || fs8.existsSync(path8.join(dir, "emulator"))) {
1828
+ if (fs9.existsSync(path9.join(dir, "cmdline-tools")) || fs9.existsSync(path9.join(dir, "platform-tools")) || fs9.existsSync(path9.join(dir, "emulator"))) {
1475
1829
  return dir;
1476
1830
  }
1477
1831
  }
@@ -1479,37 +1833,37 @@ function findSdkRoot() {
1479
1833
  }
1480
1834
  function findSdkManager(sdkRoot) {
1481
1835
  const paths = [
1482
- path8.join(sdkRoot, "cmdline-tools/latest/bin/sdkmanager"),
1483
- path8.join(sdkRoot, "cmdline-tools/bin/sdkmanager"),
1836
+ path9.join(sdkRoot, "cmdline-tools/latest/bin/sdkmanager"),
1837
+ path9.join(sdkRoot, "cmdline-tools/bin/sdkmanager"),
1484
1838
  // Homebrew installs cmdline-tools directly
1485
- path8.join(sdkRoot, "bin/sdkmanager")
1839
+ path9.join(sdkRoot, "bin/sdkmanager")
1486
1840
  ];
1487
1841
  for (const p of paths) {
1488
- if (fs8.existsSync(p)) return p;
1842
+ if (fs9.existsSync(p)) return p;
1489
1843
  }
1490
1844
  return null;
1491
1845
  }
1492
1846
  function findAvdManager(sdkRoot) {
1493
1847
  const paths = [
1494
- path8.join(sdkRoot, "cmdline-tools/latest/bin/avdmanager"),
1495
- path8.join(sdkRoot, "cmdline-tools/bin/avdmanager"),
1496
- path8.join(sdkRoot, "bin/avdmanager")
1848
+ path9.join(sdkRoot, "cmdline-tools/latest/bin/avdmanager"),
1849
+ path9.join(sdkRoot, "cmdline-tools/bin/avdmanager"),
1850
+ path9.join(sdkRoot, "bin/avdmanager")
1497
1851
  ];
1498
1852
  for (const p of paths) {
1499
- if (fs8.existsSync(p)) return p;
1853
+ if (fs9.existsSync(p)) return p;
1500
1854
  }
1501
1855
  return null;
1502
1856
  }
1503
1857
  function findEmulator(sdkRoot) {
1504
1858
  const paths = [
1505
- path8.join(sdkRoot, "emulator/emulator"),
1506
- path8.join(sdkRoot, "tools/emulator")
1859
+ path9.join(sdkRoot, "emulator/emulator"),
1860
+ path9.join(sdkRoot, "tools/emulator")
1507
1861
  ];
1508
1862
  for (const p of paths) {
1509
- if (fs8.existsSync(p)) return p;
1863
+ if (fs9.existsSync(p)) return p;
1510
1864
  }
1511
1865
  try {
1512
- (0, import_child_process4.execSync)("which emulator", { stdio: "pipe", timeout: 3e3 });
1866
+ (0, import_child_process6.execSync)("which emulator", { stdio: "pipe", timeout: 3e3 });
1513
1867
  return "emulator";
1514
1868
  } catch {
1515
1869
  return null;
@@ -1518,7 +1872,7 @@ function findEmulator(sdkRoot) {
1518
1872
  async function installSdk(log2) {
1519
1873
  if (process.platform === "darwin") {
1520
1874
  try {
1521
- (0, import_child_process4.execSync)("brew --version", { stdio: "ignore", timeout: 5e3 });
1875
+ (0, import_child_process6.execSync)("brew --version", { stdio: "ignore", timeout: 5e3 });
1522
1876
  } catch {
1523
1877
  log2("Homebrew not found. Install Android Studio manually:");
1524
1878
  log2(" https://developer.android.com/studio");
@@ -1526,7 +1880,7 @@ async function installSdk(log2) {
1526
1880
  }
1527
1881
  log2("Installing Android SDK command-line tools via Homebrew...");
1528
1882
  try {
1529
- (0, import_child_process4.execSync)("brew install --cask android-commandlinetools", {
1883
+ (0, import_child_process6.execSync)("brew install --cask android-commandlinetools", {
1530
1884
  stdio: ["ignore", "pipe", "pipe"],
1531
1885
  timeout: 3e5
1532
1886
  });
@@ -1549,13 +1903,13 @@ async function installSdk(log2) {
1549
1903
  return null;
1550
1904
  }
1551
1905
  function hasSystemImage(sdkRoot) {
1552
- const imgDir = path8.join(sdkRoot, "system-images", `android-${DEFAULT_API_LEVEL}`, "google_apis", "arm64-v8a");
1553
- return fs8.existsSync(imgDir);
1906
+ const imgDir = path9.join(sdkRoot, "system-images", `android-${DEFAULT_API_LEVEL}`, "google_apis", "arm64-v8a");
1907
+ return fs9.existsSync(imgDir);
1554
1908
  }
1555
1909
  function installSystemImage(sdkManager, log2) {
1556
1910
  log2(`Installing system image (API ${DEFAULT_API_LEVEL})... this may take a few minutes`);
1557
1911
  try {
1558
- (0, import_child_process4.execSync)(`yes | "${sdkManager}" --install "${DEFAULT_SYSTEM_IMAGE}"`, {
1912
+ (0, import_child_process6.execSync)(`yes | "${sdkManager}" --install "${DEFAULT_SYSTEM_IMAGE}"`, {
1559
1913
  stdio: ["pipe", "pipe", "pipe"],
1560
1914
  timeout: 6e5,
1561
1915
  shell: "/bin/bash"
@@ -1569,7 +1923,7 @@ function installSystemImage(sdkManager, log2) {
1569
1923
  }
1570
1924
  function listAvds(avdManager) {
1571
1925
  try {
1572
- const output = (0, import_child_process4.execSync)(`"${avdManager}" list avd -c`, {
1926
+ const output = (0, import_child_process6.execSync)(`"${avdManager}" list avd -c`, {
1573
1927
  encoding: "utf-8",
1574
1928
  timeout: 1e4,
1575
1929
  stdio: ["ignore", "pipe", "pipe"]
@@ -1582,7 +1936,7 @@ function listAvds(avdManager) {
1582
1936
  function createAvd(sdkRoot, avdManager, log2) {
1583
1937
  log2(`Creating AVD '${DEFAULT_AVD_NAME}'...`);
1584
1938
  try {
1585
- (0, import_child_process4.execSync)(
1939
+ (0, import_child_process6.execSync)(
1586
1940
  `echo no | "${avdManager}" create avd -n ${DEFAULT_AVD_NAME} -k "${DEFAULT_SYSTEM_IMAGE}" --force`,
1587
1941
  { stdio: ["pipe", "pipe", "pipe"], timeout: 3e4, shell: "/bin/bash" }
1588
1942
  );
@@ -1596,7 +1950,7 @@ function startEmulator(emulatorBin, avdName, log2, visible = false) {
1596
1950
  log2(`Starting emulator '${avdName}'${visible ? "" : " (headless)"}...`);
1597
1951
  const emulatorArgs = ["-avd", avdName, "-no-audio", "-gpu", "swiftshader_indirect"];
1598
1952
  if (!visible) emulatorArgs.push("-no-window");
1599
- const proc = (0, import_child_process4.spawn)(emulatorBin, emulatorArgs, {
1953
+ const proc = (0, import_child_process6.spawn)(emulatorBin, emulatorArgs, {
1600
1954
  stdio: "ignore",
1601
1955
  detached: true
1602
1956
  });
@@ -1607,13 +1961,13 @@ async function waitForBoot(log2, timeoutMs = 12e4) {
1607
1961
  log2("Waiting for emulator to boot...");
1608
1962
  while (Date.now() < deadline) {
1609
1963
  try {
1610
- const output = (0, import_child_process4.execSync)("adb devices", { encoding: "utf-8", timeout: 5e3 });
1964
+ const output = (0, import_child_process6.execSync)("adb devices", { encoding: "utf-8", timeout: 5e3 });
1611
1965
  const lines = output.split("\n").slice(1).filter((l) => l.includes(" device"));
1612
1966
  const emulator = lines.find((l) => l.startsWith("emulator-"));
1613
1967
  if (emulator) {
1614
1968
  const serial = emulator.split(" ")[0].trim();
1615
1969
  try {
1616
- const bootAnim = (0, import_child_process4.execSync)(`adb -s ${serial} shell getprop init.svc.bootanim`, {
1970
+ const bootAnim = (0, import_child_process6.execSync)(`adb -s ${serial} shell getprop init.svc.bootanim`, {
1617
1971
  encoding: "utf-8",
1618
1972
  timeout: 5e3
1619
1973
  }).trim();
@@ -1633,7 +1987,7 @@ async function ensureEmulator(log2, visible = false) {
1633
1987
  ensureJavaHome();
1634
1988
  const hasJava = () => {
1635
1989
  try {
1636
- (0, import_child_process4.execSync)("java -version", { stdio: "ignore", timeout: 5e3 });
1990
+ (0, import_child_process6.execSync)("java -version", { stdio: "ignore", timeout: 5e3 });
1637
1991
  return true;
1638
1992
  } catch {
1639
1993
  return false;
@@ -1642,7 +1996,7 @@ async function ensureEmulator(log2, visible = false) {
1642
1996
  if (!hasJava()) {
1643
1997
  if (process.platform === "darwin") {
1644
1998
  try {
1645
- (0, import_child_process4.execSync)("brew --version", { stdio: "ignore", timeout: 5e3 });
1999
+ (0, import_child_process6.execSync)("brew --version", { stdio: "ignore", timeout: 5e3 });
1646
2000
  } catch {
1647
2001
  throw new Error("Java is required for Android SDK.\nInstall Homebrew (https://brew.sh) then: brew install --cask temurin");
1648
2002
  }
@@ -1651,7 +2005,7 @@ async function ensureEmulator(log2, visible = false) {
1651
2005
  for (const formula of javaFormulas) {
1652
2006
  try {
1653
2007
  const cmd = formula === "openjdk" ? `brew install ${formula}` : `brew install --cask ${formula}`;
1654
- (0, import_child_process4.execSync)(cmd, { stdio: ["ignore", "pipe", "pipe"], timeout: 3e5 });
2008
+ (0, import_child_process6.execSync)(cmd, { stdio: ["ignore", "pipe", "pipe"], timeout: 3e5 });
1655
2009
  } catch {
1656
2010
  }
1657
2011
  if (formula.startsWith("openjdk")) {
@@ -1682,7 +2036,7 @@ async function ensureEmulator(log2, visible = false) {
1682
2036
  const avdManager = findAvdManager(sdkRoot);
1683
2037
  if (sdkManager) {
1684
2038
  try {
1685
- (0, import_child_process4.execSync)(`yes | "${sdkManager}" --licenses`, {
2039
+ (0, import_child_process6.execSync)(`yes | "${sdkManager}" --licenses`, {
1686
2040
  stdio: ["pipe", "pipe", "pipe"],
1687
2041
  timeout: 6e4,
1688
2042
  shell: "/bin/bash"
@@ -1693,7 +2047,7 @@ async function ensureEmulator(log2, visible = false) {
1693
2047
  if (!emulatorBin && sdkManager) {
1694
2048
  log2("Installing Android emulator and build tools...");
1695
2049
  try {
1696
- (0, import_child_process4.execSync)(
2050
+ (0, import_child_process6.execSync)(
1697
2051
  `"${sdkManager}" --install "emulator" "platform-tools" "platforms;android-${DEFAULT_API_LEVEL}" "build-tools;${DEFAULT_API_LEVEL}.0.0"`,
1698
2052
  { stdio: ["pipe", "pipe", "pipe"], timeout: 3e5 }
1699
2053
  );
@@ -1725,13 +2079,13 @@ async function ensureEmulator(log2, visible = false) {
1725
2079
  startEmulator(emulatorBin, DEFAULT_AVD_NAME, log2, visible);
1726
2080
  return waitForBoot(log2);
1727
2081
  }
1728
- var import_child_process4, fs8, path8, DEFAULT_AVD_NAME, DEFAULT_API_LEVEL, DEFAULT_SYSTEM_IMAGE;
2082
+ var import_child_process6, fs9, path9, DEFAULT_AVD_NAME, DEFAULT_API_LEVEL, DEFAULT_SYSTEM_IMAGE;
1729
2083
  var init_emulator = __esm({
1730
2084
  "src/app/android/emulator.ts"() {
1731
2085
  "use strict";
1732
- import_child_process4 = require("child_process");
1733
- fs8 = __toESM(require("fs"), 1);
1734
- path8 = __toESM(require("path"), 1);
2086
+ import_child_process6 = require("child_process");
2087
+ fs9 = __toESM(require("fs"), 1);
2088
+ path9 = __toESM(require("path"), 1);
1735
2089
  DEFAULT_AVD_NAME = "browse_default";
1736
2090
  DEFAULT_API_LEVEL = "35";
1737
2091
  DEFAULT_SYSTEM_IMAGE = `system-images;android-${DEFAULT_API_LEVEL};google_apis;arm64-v8a`;
@@ -1753,396 +2107,217 @@ function resolveStateDir() {
1753
2107
  if (localDir) return localDir;
1754
2108
  let dir = process.cwd();
1755
2109
  for (let i = 0; i < 20; i++) {
1756
- if (fs9.existsSync(path9.join(dir, ".git")) || fs9.existsSync(path9.join(dir, ".claude"))) {
1757
- const browseDir = path9.join(dir, ".browse");
1758
- fs9.mkdirSync(browseDir, { recursive: true });
2110
+ if (fs10.existsSync(path10.join(dir, ".git")) || fs10.existsSync(path10.join(dir, ".claude"))) {
2111
+ const browseDir = path10.join(dir, ".browse");
2112
+ fs10.mkdirSync(browseDir, { recursive: true });
1759
2113
  return browseDir;
1760
2114
  }
1761
- const parent = path9.dirname(dir);
2115
+ const parent = path10.dirname(dir);
1762
2116
  if (parent === dir) break;
1763
2117
  dir = parent;
1764
2118
  }
1765
2119
  return "/tmp";
1766
2120
  }
1767
2121
  function stateFilePath() {
1768
- return path9.join(resolveStateDir(), "android-state.json");
2122
+ return path10.join(resolveStateDir(), "android-state.json");
1769
2123
  }
1770
2124
  function readState() {
1771
2125
  try {
1772
- return JSON.parse(fs9.readFileSync(stateFilePath(), "utf-8"));
2126
+ return JSON.parse(fs10.readFileSync(stateFilePath(), "utf-8"));
1773
2127
  } catch {
1774
2128
  return null;
1775
2129
  }
1776
2130
  }
1777
2131
  function writeState(state) {
1778
- const dir = path9.dirname(stateFilePath());
1779
- fs9.mkdirSync(dir, { recursive: true });
1780
- fs9.writeFileSync(stateFilePath(), JSON.stringify(state, null, 2));
1781
- }
1782
- function clearState() {
1783
- try {
1784
- fs9.unlinkSync(stateFilePath());
1785
- } catch {
1786
- }
1787
- }
1788
- async function checkHealth(port = DRIVER_PORT2) {
1789
- try {
1790
- const resp = await fetch(`http://127.0.0.1:${port}/health`, { signal: AbortSignal.timeout(2e3) });
1791
- return resp.ok;
1792
- } catch {
1793
- return false;
1794
- }
1795
- }
1796
- async function status() {
1797
- const state = readState();
1798
- if (!state) return { running: false, state: null, healthy: false };
1799
- const healthy = await checkHealth(state.port);
1800
- if (!healthy) {
1801
- clearState();
1802
- return { running: false, state, healthy: false };
1803
- }
1804
- return { running: true, state, healthy: true };
1805
- }
1806
- async function stopDriver() {
1807
- const state = readState();
1808
- if (!state) return;
1809
- try {
1810
- (0, import_child_process5.execSync)(`adb -s ${state.serial} shell am force-stop io.ulpi.browse.driver.test`, { stdio: "pipe", timeout: 5e3 });
1811
- (0, import_child_process5.execSync)(`adb -s ${state.serial} shell am force-stop io.ulpi.browse.driver`, { stdio: "pipe", timeout: 5e3 });
1812
- (0, import_child_process5.execSync)(`adb -s ${state.serial} forward --remove tcp:${state.port}`, { stdio: "pipe", timeout: 5e3 });
1813
- } catch {
1814
- }
1815
- clearState();
1816
- }
1817
- async function stop() {
1818
- const state = readState();
1819
- if (!state) return "No Android device/emulator running.";
1820
- try {
1821
- (0, import_child_process5.execSync)(`adb -s ${state.serial} shell am force-stop io.ulpi.browse.driver.test`, { stdio: "pipe", timeout: 5e3 });
1822
- (0, import_child_process5.execSync)(`adb -s ${state.serial} shell am force-stop io.ulpi.browse.driver`, { stdio: "pipe", timeout: 5e3 });
1823
- (0, import_child_process5.execSync)(`adb -s ${state.serial} forward --remove tcp:${state.port}`, { stdio: "pipe", timeout: 5e3 });
1824
- if (state.serial.startsWith("emulator-")) {
1825
- (0, import_child_process5.execSync)(`adb -s ${state.serial} emu kill`, { stdio: "pipe", timeout: 1e4 });
1826
- }
1827
- } catch {
1828
- }
1829
- clearState();
1830
- return `Android stopped (${state.device}).`;
1831
- }
1832
- async function startAndroid(opts = {}) {
1833
- const log2 = opts.log || ((msg) => process.stderr.write(`[browse] ${msg}
1834
- `));
1835
- const port = DRIVER_PORT2;
1836
- const existing = readState();
1837
- if (existing) {
1838
- const healthy = await checkHealth(existing.port);
1839
- if (healthy) {
1840
- if (opts.app && existing.app !== opts.app) {
1841
- log2(`Switching to ${opts.app}...`);
1842
- await stopDriver();
1843
- } else {
1844
- return existing;
1845
- }
1846
- }
1847
- log2("Cleaning up stale driver...");
1848
- await stopDriver();
1849
- }
1850
- log2("Finding Android device...");
1851
- const { ensureAndroidBridge: ensureAndroidBridge2, createAndroidBridge: createAndroidBridge2, AdbNotFoundError: AdbNotFoundError2, installAdb: installAdb2 } = await Promise.resolve().then(() => (init_bridge(), bridge_exports));
1852
- let serial;
1853
- try {
1854
- serial = await ensureAndroidBridge2(opts.device);
1855
- } catch (err) {
1856
- if (err instanceof AdbNotFoundError2) {
1857
- log2("adb not found. Attempting to install...");
1858
- const installed = await installAdb2(log2);
1859
- if (!installed) throw err;
1860
- try {
1861
- serial = await ensureAndroidBridge2(opts.device);
1862
- } catch (retryErr) {
1863
- if (retryErr.message?.includes("No booted Android device")) {
1864
- const { ensureEmulator: ensureEmulator2 } = await Promise.resolve().then(() => (init_emulator(), emulator_exports));
1865
- serial = await ensureEmulator2(log2, opts.visible);
1866
- } else {
1867
- throw retryErr;
1868
- }
1869
- }
1870
- } else if (err.message?.includes("No booted Android device")) {
1871
- const { ensureEmulator: ensureEmulator2 } = await Promise.resolve().then(() => (init_emulator(), emulator_exports));
1872
- serial = await ensureEmulator2(log2, opts.visible);
1873
- } else {
1874
- throw err;
1875
- }
1876
- }
1877
- let deviceName = serial;
1878
- try {
1879
- deviceName = (0, import_child_process5.execSync)(`adb -s ${serial} shell getprop ro.product.model`, {
1880
- encoding: "utf-8",
1881
- timeout: 5e3,
1882
- stdio: ["ignore", "pipe", "pipe"]
1883
- }).trim() || serial;
1884
- } catch {
1885
- }
1886
- const driverApkDir = path9.resolve(__dirname3, "../../../browse-android");
1887
- const apkPath = path9.join(driverApkDir, "app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk");
1888
- if (!fs9.existsSync(apkPath) && fs9.existsSync(path9.join(driverApkDir, "gradlew"))) {
1889
- log2("Building Android driver APK...");
1890
- try {
1891
- const { findSdkRoot: findSdkRoot2, ensureJavaHome: ensureJavaHome2 } = await Promise.resolve().then(() => (init_emulator(), emulator_exports));
1892
- ensureJavaHome2();
1893
- if (!process.env.ANDROID_HOME) {
1894
- const sdkRoot = findSdkRoot2();
1895
- if (sdkRoot) process.env.ANDROID_HOME = sdkRoot;
1896
- }
1897
- (0, import_child_process5.execSync)("./gradlew :app:assembleDebug :app:assembleDebugAndroidTest --stacktrace", {
1898
- cwd: driverApkDir,
1899
- stdio: ["ignore", "pipe", "pipe"],
1900
- timeout: 3e5
1901
- });
1902
- log2("APK built.");
1903
- } catch (err) {
1904
- const stdout = err.stdout?.toString() || "";
1905
- const stderr = err.stderr?.toString() || "";
1906
- const combined = stdout + "\n" + stderr;
1907
- const lines = combined.split("\n");
1908
- const errorLines = lines.filter(
1909
- (l) => !l.startsWith(" at ") && !l.startsWith(" at ") && (l.includes("error") || l.includes("Error") || l.includes("FAILURE") || l.includes("Could not") || l.includes("wrong") || l.includes("Cannot") || l.includes("SDK") || l.includes("missing"))
1910
- );
1911
- log2(`APK build failed:
1912
- ${errorLines.slice(0, 10).join("\n") || lines.slice(0, 15).join("\n")}`);
1913
- }
1914
- }
1915
- const targetApp = opts.app || "com.android.settings";
1916
- log2(`Launching ${targetApp}...`);
1917
- try {
1918
- (0, import_child_process5.execSync)(`adb -s ${serial} shell am start -a android.intent.action.MAIN -c android.intent.category.LAUNCHER -p ${targetApp}`, {
1919
- stdio: "pipe",
1920
- timeout: 1e4
1921
- });
1922
- } catch {
1923
- try {
1924
- (0, import_child_process5.execSync)(`adb -s ${serial} shell monkey -p ${targetApp} -c android.intent.category.LAUNCHER 1`, {
1925
- stdio: "pipe",
1926
- timeout: 1e4
1927
- });
1928
- } catch {
1929
- }
1930
- }
1931
- await sleep(2e3);
1932
- log2("Starting driver...");
1933
- await createAndroidBridge2(serial, targetApp);
1934
- const state = {
1935
- platform: "android",
1936
- device: deviceName,
1937
- serial,
1938
- app: targetApp,
1939
- port,
1940
- startedAt: (/* @__PURE__ */ new Date()).toISOString()
1941
- };
1942
- writeState(state);
1943
- return state;
1944
- }
1945
- var fs9, path9, import_child_process5, import_url3, __dirname3, DRIVER_PORT2, sleep;
1946
- var init_sim_service = __esm({
1947
- "src/app/android/sim-service.ts"() {
1948
- "use strict";
1949
- fs9 = __toESM(require("fs"), 1);
1950
- path9 = __toESM(require("path"), 1);
1951
- import_child_process5 = require("child_process");
1952
- import_url3 = require("url");
1953
- __dirname3 = path9.dirname((0, import_url3.fileURLToPath)(__import_meta_url));
1954
- DRIVER_PORT2 = 7779;
1955
- sleep = (ms) => new Promise((r2) => setTimeout(r2, ms));
1956
- }
1957
- });
1958
-
1959
- // src/app/ios/controller.ts
1960
- var controller_exports = {};
1961
- __export(controller_exports, {
1962
- addMedia: () => addMedia,
1963
- bootSimulator: () => bootSimulator,
1964
- checkXcodeTools: () => checkXcodeTools,
1965
- clearStatusBar: () => clearStatusBar,
1966
- getAppContainer: () => getAppContainer,
1967
- grantPermission: () => grantPermission,
1968
- installApp: () => installApp,
1969
- isAppInstalled: () => isAppInstalled,
1970
- launchApp: () => launchApp,
1971
- listSimulators: () => listSimulators,
1972
- openURL: () => openURL,
1973
- resetPermissions: () => resetPermissions,
1974
- resolveSimulator: () => resolveSimulator,
1975
- revokePermission: () => revokePermission,
1976
- screenshotSimulator: () => screenshotSimulator,
1977
- setStatusBar: () => setStatusBar,
1978
- shutdownSimulator: () => shutdownSimulator,
1979
- terminateApp: () => terminateApp,
1980
- uninstallApp: () => uninstallApp
1981
- });
1982
- async function simctl(...args) {
1983
- try {
1984
- const { stdout } = await execFileAsync("xcrun", ["simctl", ...args], {
1985
- timeout: 6e4,
1986
- maxBuffer: 10 * 1024 * 1024
1987
- // 10 MB for device list JSON
1988
- });
1989
- return stdout.trim();
1990
- } catch (err) {
1991
- const stderr = err.stderr?.trim() || "";
1992
- const message = stderr || err.message || "Unknown simctl error";
1993
- throw new Error(`simctl ${args[0]} failed: ${message}`);
1994
- }
1995
- }
1996
- async function listSimulators(filter) {
1997
- const raw = await simctl("list", "devices", "--json");
1998
- const parsed = JSON.parse(raw);
1999
- const results = [];
2000
- for (const [runtime, devices] of Object.entries(parsed.devices)) {
2001
- for (const device of devices) {
2002
- if (!device.isAvailable) continue;
2003
- const info = {
2004
- udid: device.udid,
2005
- name: device.name,
2006
- state: device.state,
2007
- runtime,
2008
- isAvailable: device.isAvailable
2009
- };
2010
- if (!filter || info.name.toLowerCase().includes(filter.toLowerCase()) || info.runtime.toLowerCase().includes(filter.toLowerCase())) {
2011
- results.push(info);
2012
- }
2013
- }
2014
- }
2015
- return results;
2016
- }
2017
- async function resolveSimulator(identifier) {
2018
- const all = await listSimulators();
2019
- if (identifier) {
2020
- const byUdid = all.find((s) => s.udid === identifier);
2021
- if (byUdid) return byUdid;
2022
- const byName = all.filter((s) => s.name === identifier && s.isAvailable);
2023
- if (byName.length === 1) return byName[0];
2024
- if (byName.length > 1) {
2025
- const booted2 = byName.find((s) => s.state === "Booted");
2026
- if (booted2) return booted2;
2027
- return byName[0];
2028
- }
2029
- const fuzzy = all.filter((s) => s.name.includes(identifier) && s.isAvailable);
2030
- if (fuzzy.length === 1) return fuzzy[0];
2031
- if (fuzzy.length > 1) return fuzzy.find((s) => s.state === "Booted") || fuzzy[0];
2032
- throw new Error(`Simulator '${identifier}' not found. Run: xcrun simctl list devices available`);
2033
- }
2034
- const booted = all.find((s) => s.state === "Booted");
2035
- if (booted) return booted;
2036
- if (all.length === 0) {
2037
- throw new Error(
2038
- 'No iOS Simulators available. Create one with:\n xcrun simctl create "iPhone 16 Pro" "com.apple.CoreSimulator.SimDeviceType.iPhone-16-Pro"'
2039
- );
2040
- }
2041
- return all[0];
2042
- }
2043
- async function bootSimulator(udid) {
2044
- const sims = await listSimulators();
2045
- const sim = sims.find((s) => s.udid === udid);
2046
- if (sim?.state === "Booted") return;
2047
- await simctl("boot", udid);
2048
- const deadline = Date.now() + 3e4;
2049
- while (Date.now() < deadline) {
2050
- const current = await listSimulators();
2051
- const updated = current.find((s) => s.udid === udid);
2052
- if (updated?.state === "Booted") return;
2053
- await new Promise((r2) => setTimeout(r2, 1e3));
2054
- }
2055
- throw new Error(`Simulator ${udid} did not boot within 30 seconds`);
2056
- }
2057
- async function shutdownSimulator(udid) {
2058
- await simctl("shutdown", udid);
2059
- }
2060
- async function installApp(udid, appPath) {
2061
- await simctl("install", udid, appPath);
2062
- }
2063
- async function uninstallApp(udid, bundleId) {
2064
- await simctl("uninstall", udid, bundleId);
2065
- }
2066
- async function launchApp(udid, bundleId, env) {
2067
- const args = ["launch", udid, bundleId];
2068
- if (env && Object.keys(env).length > 0) {
2069
- for (const [key, value] of Object.entries(env)) {
2070
- args.push(`SIMCTL_CHILD_${key}=${value}`);
2071
- }
2072
- }
2073
- await simctl(...args);
2132
+ const dir = path10.dirname(stateFilePath());
2133
+ fs10.mkdirSync(dir, { recursive: true });
2134
+ fs10.writeFileSync(stateFilePath(), JSON.stringify(state, null, 2));
2074
2135
  }
2075
- async function terminateApp(udid, bundleId) {
2136
+ function clearState() {
2076
2137
  try {
2077
- await simctl("terminate", udid, bundleId);
2138
+ fs10.unlinkSync(stateFilePath());
2078
2139
  } catch {
2079
2140
  }
2080
2141
  }
2081
- async function isAppInstalled(udid, bundleId) {
2142
+ async function checkHealth(port = DRIVER_PORT2) {
2082
2143
  try {
2083
- await simctl("get_app_container", udid, bundleId);
2084
- return true;
2144
+ const resp = await fetch(`http://127.0.0.1:${port}/health`, { signal: AbortSignal.timeout(2e3) });
2145
+ return resp.ok;
2085
2146
  } catch {
2086
2147
  return false;
2087
2148
  }
2088
2149
  }
2089
- async function grantPermission(udid, bundleId, permission) {
2090
- await simctl("privacy", udid, "grant", permission, bundleId);
2091
- }
2092
- async function revokePermission(udid, bundleId, permission) {
2093
- await simctl("privacy", udid, "revoke", permission, bundleId);
2094
- }
2095
- async function resetPermissions(udid, bundleId) {
2096
- await simctl("privacy", udid, "reset", "all", bundleId);
2097
- }
2098
- async function screenshotSimulator(udid, outputPath) {
2099
- await simctl("io", udid, "screenshot", outputPath);
2100
- }
2101
- async function getAppContainer(udid, bundleId, container = "data") {
2102
- return simctl("get_app_container", udid, bundleId, container);
2103
- }
2104
- async function openURL(udid, url) {
2105
- await simctl("openurl", udid, url);
2106
- }
2107
- async function addMedia(udid, ...paths) {
2108
- await simctl("addmedia", udid, ...paths);
2150
+ async function status() {
2151
+ const state = readState();
2152
+ if (!state) return { running: false, state: null, healthy: false };
2153
+ const healthy = await checkHealth(state.port);
2154
+ if (!healthy) {
2155
+ clearState();
2156
+ return { running: false, state, healthy: false };
2157
+ }
2158
+ return { running: true, state, healthy: true };
2109
2159
  }
2110
- async function setStatusBar(udid, overrides) {
2111
- const args = ["status_bar", udid, "override"];
2112
- if (overrides.time) args.push("--time", overrides.time);
2113
- if (overrides.batteryLevel !== void 0) args.push("--batteryLevel", String(overrides.batteryLevel));
2114
- if (overrides.batteryState) args.push("--batteryState", overrides.batteryState);
2115
- if (overrides.cellularBars !== void 0) args.push("--cellularBars", String(overrides.cellularBars));
2116
- if (overrides.wifiBars !== void 0) args.push("--wifiBars", String(overrides.wifiBars));
2117
- if (overrides.operatorName) args.push("--operatorName", overrides.operatorName);
2118
- await simctl(...args);
2160
+ async function stopDriver() {
2161
+ const state = readState();
2162
+ if (!state) return;
2163
+ try {
2164
+ (0, import_child_process7.execSync)(`adb -s ${state.serial} shell am force-stop io.ulpi.browse.driver.test`, { stdio: "pipe", timeout: 5e3 });
2165
+ (0, import_child_process7.execSync)(`adb -s ${state.serial} shell am force-stop io.ulpi.browse.driver`, { stdio: "pipe", timeout: 5e3 });
2166
+ (0, import_child_process7.execSync)(`adb -s ${state.serial} forward --remove tcp:${state.port}`, { stdio: "pipe", timeout: 5e3 });
2167
+ } catch {
2168
+ }
2169
+ clearState();
2119
2170
  }
2120
- async function clearStatusBar(udid) {
2121
- await simctl("status_bar", udid, "clear");
2171
+ async function stop() {
2172
+ const state = readState();
2173
+ if (!state) return "No Android device/emulator running.";
2174
+ try {
2175
+ (0, import_child_process7.execSync)(`adb -s ${state.serial} shell am force-stop io.ulpi.browse.driver.test`, { stdio: "pipe", timeout: 5e3 });
2176
+ (0, import_child_process7.execSync)(`adb -s ${state.serial} shell am force-stop io.ulpi.browse.driver`, { stdio: "pipe", timeout: 5e3 });
2177
+ (0, import_child_process7.execSync)(`adb -s ${state.serial} forward --remove tcp:${state.port}`, { stdio: "pipe", timeout: 5e3 });
2178
+ if (state.serial.startsWith("emulator-")) {
2179
+ (0, import_child_process7.execSync)(`adb -s ${state.serial} emu kill`, { stdio: "pipe", timeout: 1e4 });
2180
+ }
2181
+ } catch {
2182
+ }
2183
+ clearState();
2184
+ return `Android stopped (${state.device}).`;
2122
2185
  }
2123
- async function checkXcodeTools() {
2186
+ async function startAndroid(opts = {}) {
2187
+ const log2 = opts.log || ((msg) => process.stderr.write(`[browse] ${msg}
2188
+ `));
2189
+ const port = DRIVER_PORT2;
2190
+ let pendingApkInstall = null;
2191
+ if (opts.app) {
2192
+ const { isAppFilePath: isAppFilePath2 } = await Promise.resolve().then(() => (init_resolve_app(), resolve_app_exports));
2193
+ if (isAppFilePath2(opts.app)) {
2194
+ pendingApkInstall = path10.resolve(opts.app);
2195
+ }
2196
+ }
2197
+ const existing = readState();
2198
+ if (existing) {
2199
+ const healthy = await checkHealth(existing.port);
2200
+ if (healthy) {
2201
+ if (opts.app && existing.app !== opts.app) {
2202
+ log2(`Switching to ${opts.app}...`);
2203
+ await stopDriver();
2204
+ } else {
2205
+ return existing;
2206
+ }
2207
+ }
2208
+ log2("Cleaning up stale driver...");
2209
+ await stopDriver();
2210
+ }
2211
+ log2("Finding Android device...");
2212
+ const { ensureAndroidBridge: ensureAndroidBridge2, createAndroidBridge: createAndroidBridge2, AdbNotFoundError: AdbNotFoundError2, installAdb: installAdb2 } = await Promise.resolve().then(() => (init_bridge(), bridge_exports));
2213
+ let serial;
2124
2214
  try {
2125
- await execFileAsync("xcrun", ["--version"], { timeout: 5e3 });
2215
+ serial = await ensureAndroidBridge2(opts.device);
2216
+ } catch (err) {
2217
+ if (err instanceof AdbNotFoundError2) {
2218
+ log2("adb not found. Attempting to install...");
2219
+ const installed = await installAdb2(log2);
2220
+ if (!installed) throw err;
2221
+ try {
2222
+ serial = await ensureAndroidBridge2(opts.device);
2223
+ } catch (retryErr) {
2224
+ if (retryErr.message?.includes("No booted Android device")) {
2225
+ const { ensureEmulator: ensureEmulator2 } = await Promise.resolve().then(() => (init_emulator(), emulator_exports));
2226
+ serial = await ensureEmulator2(log2, opts.visible);
2227
+ } else {
2228
+ throw retryErr;
2229
+ }
2230
+ }
2231
+ } else if (err.message?.includes("No booted Android device")) {
2232
+ const { ensureEmulator: ensureEmulator2 } = await Promise.resolve().then(() => (init_emulator(), emulator_exports));
2233
+ serial = await ensureEmulator2(log2, opts.visible);
2234
+ } else {
2235
+ throw err;
2236
+ }
2237
+ }
2238
+ let deviceName = serial;
2239
+ try {
2240
+ deviceName = (0, import_child_process7.execSync)(`adb -s ${serial} shell getprop ro.product.model`, {
2241
+ encoding: "utf-8",
2242
+ timeout: 5e3,
2243
+ stdio: ["ignore", "pipe", "pipe"]
2244
+ }).trim() || serial;
2126
2245
  } catch {
2127
- throw new Error(
2128
- "Xcode Command Line Tools not found.\nInstall with: xcode-select --install\nOr install Xcode from the Mac App Store."
2129
- );
2130
2246
  }
2247
+ const driverApkDir = path10.resolve(__dirname3, "../../../browse-android");
2248
+ const apkPath = path10.join(driverApkDir, "app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk");
2249
+ if (!fs10.existsSync(apkPath) && fs10.existsSync(path10.join(driverApkDir, "gradlew"))) {
2250
+ log2("Building Android driver APK...");
2251
+ try {
2252
+ const { findSdkRoot: findSdkRoot2, ensureJavaHome: ensureJavaHome2 } = await Promise.resolve().then(() => (init_emulator(), emulator_exports));
2253
+ ensureJavaHome2();
2254
+ if (!process.env.ANDROID_HOME) {
2255
+ const sdkRoot = findSdkRoot2();
2256
+ if (sdkRoot) process.env.ANDROID_HOME = sdkRoot;
2257
+ }
2258
+ (0, import_child_process7.execSync)("./gradlew :app:assembleDebug :app:assembleDebugAndroidTest --stacktrace", {
2259
+ cwd: driverApkDir,
2260
+ stdio: ["ignore", "pipe", "pipe"],
2261
+ timeout: 3e5
2262
+ });
2263
+ log2("APK built.");
2264
+ } catch (err) {
2265
+ const stdout = err.stdout?.toString() || "";
2266
+ const stderr = err.stderr?.toString() || "";
2267
+ const combined = stdout + "\n" + stderr;
2268
+ const lines = combined.split("\n");
2269
+ const errorLines = lines.filter(
2270
+ (l) => !l.startsWith(" at ") && !l.startsWith(" at ") && (l.includes("error") || l.includes("Error") || l.includes("FAILURE") || l.includes("Could not") || l.includes("wrong") || l.includes("Cannot") || l.includes("SDK") || l.includes("missing"))
2271
+ );
2272
+ log2(`APK build failed:
2273
+ ${errorLines.slice(0, 10).join("\n") || lines.slice(0, 15).join("\n")}`);
2274
+ }
2275
+ }
2276
+ if (pendingApkInstall) {
2277
+ const { resolveAndroidApp: resolveAndroidApp2 } = await Promise.resolve().then(() => (init_resolve_app(), resolve_app_exports));
2278
+ opts.app = await resolveAndroidApp2(pendingApkInstall, serial, log2);
2279
+ }
2280
+ const targetApp = opts.app || "com.android.settings";
2281
+ log2(`Launching ${targetApp}...`);
2131
2282
  try {
2132
- await execFileAsync("xcrun", ["simctl", "help"], { timeout: 5e3 });
2283
+ (0, import_child_process7.execSync)(`adb -s ${serial} shell am start -a android.intent.action.MAIN -c android.intent.category.LAUNCHER -p ${targetApp}`, {
2284
+ stdio: "pipe",
2285
+ timeout: 1e4
2286
+ });
2133
2287
  } catch {
2134
- throw new Error(
2135
- "simctl not available. Ensure Xcode is installed and selected:\n sudo xcode-select -s /Applications/Xcode.app"
2136
- );
2288
+ try {
2289
+ (0, import_child_process7.execSync)(`adb -s ${serial} shell monkey -p ${targetApp} -c android.intent.category.LAUNCHER 1`, {
2290
+ stdio: "pipe",
2291
+ timeout: 1e4
2292
+ });
2293
+ } catch {
2294
+ }
2137
2295
  }
2296
+ await sleep(2e3);
2297
+ log2("Starting driver...");
2298
+ await createAndroidBridge2(serial, targetApp);
2299
+ const state = {
2300
+ platform: "android",
2301
+ device: deviceName,
2302
+ serial,
2303
+ app: targetApp,
2304
+ port,
2305
+ startedAt: (/* @__PURE__ */ new Date()).toISOString()
2306
+ };
2307
+ writeState(state);
2308
+ return state;
2138
2309
  }
2139
- var import_child_process6, import_util, execFileAsync;
2140
- var init_controller = __esm({
2141
- "src/app/ios/controller.ts"() {
2310
+ var fs10, path10, import_child_process7, import_url3, __dirname3, DRIVER_PORT2, sleep;
2311
+ var init_sim_service = __esm({
2312
+ "src/app/android/sim-service.ts"() {
2142
2313
  "use strict";
2143
- import_child_process6 = require("child_process");
2144
- import_util = require("util");
2145
- execFileAsync = (0, import_util.promisify)(import_child_process6.execFile);
2314
+ fs10 = __toESM(require("fs"), 1);
2315
+ path10 = __toESM(require("path"), 1);
2316
+ import_child_process7 = require("child_process");
2317
+ import_url3 = require("url");
2318
+ __dirname3 = path10.dirname((0, import_url3.fileURLToPath)(__import_meta_url));
2319
+ DRIVER_PORT2 = 7779;
2320
+ sleep = (ms) => new Promise((r2) => setTimeout(r2, ms));
2146
2321
  }
2147
2322
  });
2148
2323
 
@@ -2299,11 +2474,11 @@ function createIOSBridge(udid, bundleId, port = DEFAULT_RUNNER_PORT) {
2299
2474
  const raw = await runnerRequest(port, "/tree");
2300
2475
  return convertTree(raw);
2301
2476
  },
2302
- async action(path24, actionName) {
2303
- return runnerRequest(port, "/action", { path: path24, actionName });
2477
+ async action(path25, actionName) {
2478
+ return runnerRequest(port, "/action", { path: path25, actionName });
2304
2479
  },
2305
- async setValue(path24, value) {
2306
- return runnerRequest(port, "/set-value", { path: path24, value });
2480
+ async setValue(path25, value) {
2481
+ return runnerRequest(port, "/set-value", { path: path25, value });
2307
2482
  },
2308
2483
  async type(text) {
2309
2484
  return runnerRequest(port, "/type", { text });
@@ -2359,35 +2534,35 @@ function resolveStateDir2() {
2359
2534
  if (localDir) return localDir;
2360
2535
  let dir = process.cwd();
2361
2536
  for (let i = 0; i < 20; i++) {
2362
- if (fs10.existsSync(path10.join(dir, ".git")) || fs10.existsSync(path10.join(dir, ".claude"))) {
2363
- const browseDir = path10.join(dir, ".browse");
2364
- fs10.mkdirSync(browseDir, { recursive: true });
2537
+ if (fs11.existsSync(path11.join(dir, ".git")) || fs11.existsSync(path11.join(dir, ".claude"))) {
2538
+ const browseDir = path11.join(dir, ".browse");
2539
+ fs11.mkdirSync(browseDir, { recursive: true });
2365
2540
  return browseDir;
2366
2541
  }
2367
- const parent = path10.dirname(dir);
2542
+ const parent = path11.dirname(dir);
2368
2543
  if (parent === dir) break;
2369
2544
  dir = parent;
2370
2545
  }
2371
2546
  return "/tmp";
2372
2547
  }
2373
2548
  function stateFilePath2() {
2374
- return path10.join(resolveStateDir2(), "sim-state.json");
2549
+ return path11.join(resolveStateDir2(), "sim-state.json");
2375
2550
  }
2376
2551
  function readState2() {
2377
2552
  try {
2378
- return JSON.parse(fs10.readFileSync(stateFilePath2(), "utf-8"));
2553
+ return JSON.parse(fs11.readFileSync(stateFilePath2(), "utf-8"));
2379
2554
  } catch {
2380
2555
  return null;
2381
2556
  }
2382
2557
  }
2383
2558
  function writeState2(state) {
2384
- const dir = path10.dirname(stateFilePath2());
2385
- fs10.mkdirSync(dir, { recursive: true });
2386
- fs10.writeFileSync(stateFilePath2(), JSON.stringify(state, null, 2));
2559
+ const dir = path11.dirname(stateFilePath2());
2560
+ fs11.mkdirSync(dir, { recursive: true });
2561
+ fs11.writeFileSync(stateFilePath2(), JSON.stringify(state, null, 2));
2387
2562
  }
2388
2563
  function clearState2() {
2389
2564
  try {
2390
- fs10.unlinkSync(stateFilePath2());
2565
+ fs11.unlinkSync(stateFilePath2());
2391
2566
  } catch {
2392
2567
  }
2393
2568
  }
@@ -2401,11 +2576,11 @@ async function checkHealth2(port = DEFAULT_PORT) {
2401
2576
  }
2402
2577
  async function configureTarget(port, bundleId, udid) {
2403
2578
  if (bundleId !== "io.ulpi.browse-ios-runner") {
2404
- const { execSync: execSync7 } = await import("child_process");
2579
+ const { execSync: execSync8 } = await import("child_process");
2405
2580
  const deviceId = udid || readState2()?.udid;
2406
2581
  if (deviceId) {
2407
2582
  try {
2408
- execSync7(`xcrun simctl launch ${deviceId} ${bundleId}`, { stdio: "pipe", timeout: 3e4 });
2583
+ execSync8(`xcrun simctl launch ${deviceId} ${bundleId}`, { stdio: "pipe", timeout: 3e4 });
2409
2584
  } catch {
2410
2585
  }
2411
2586
  }
@@ -2424,7 +2599,7 @@ async function status2() {
2424
2599
  return { running: true, state, healthy: true };
2425
2600
  }
2426
2601
  async function killStaleRunners(port, statePid) {
2427
- const { execSync: execSync7 } = await import("child_process");
2602
+ const { execSync: execSync8 } = await import("child_process");
2428
2603
  if (statePid) {
2429
2604
  try {
2430
2605
  process.kill(statePid, "SIGKILL");
@@ -2432,11 +2607,11 @@ async function killStaleRunners(port, statePid) {
2432
2607
  }
2433
2608
  }
2434
2609
  try {
2435
- execSync7('pkill -9 -f "xcodebuild.*BrowseRunnerApp"', { stdio: "pipe", timeout: 5e3 });
2610
+ execSync8('pkill -9 -f "xcodebuild.*BrowseRunnerApp"', { stdio: "pipe", timeout: 5e3 });
2436
2611
  } catch {
2437
2612
  }
2438
2613
  try {
2439
- const lsof = execSync7(`lsof -ti :${port}`, { stdio: "pipe", timeout: 5e3 }).toString().trim();
2614
+ const lsof = execSync8(`lsof -ti :${port}`, { stdio: "pipe", timeout: 5e3 }).toString().trim();
2440
2615
  for (const pid of lsof.split("\n").filter(Boolean)) {
2441
2616
  try {
2442
2617
  process.kill(parseInt(pid, 10), "SIGKILL");
@@ -2448,7 +2623,7 @@ async function killStaleRunners(port, statePid) {
2448
2623
  for (let i = 0; i < 5; i++) {
2449
2624
  await sleep2(1e3);
2450
2625
  try {
2451
- execSync7(`lsof -ti :${port}`, { stdio: "pipe", timeout: 2e3 });
2626
+ execSync8(`lsof -ti :${port}`, { stdio: "pipe", timeout: 2e3 });
2452
2627
  } catch {
2453
2628
  break;
2454
2629
  }
@@ -2465,6 +2640,14 @@ async function startIOS(opts = {}) {
2465
2640
  const log2 = opts.log || ((msg) => process.stderr.write(`[browse] ${msg}
2466
2641
  `));
2467
2642
  const port = parseInt(process.env.BROWSE_RUNNER_PORT || "", 10) || DEFAULT_PORT;
2643
+ if (opts.app) {
2644
+ const { isAppFilePath: isAppFilePath2, resolveIOSApp: resolveIOSApp2 } = await Promise.resolve().then(() => (init_resolve_app(), resolve_app_exports));
2645
+ if (isAppFilePath2(opts.app)) {
2646
+ const sim2 = await resolveSimulator(opts.device);
2647
+ if (sim2.state !== "Booted") await bootSimulator(sim2.udid);
2648
+ opts.app = await resolveIOSApp2(opts.app, sim2.udid, log2);
2649
+ }
2650
+ }
2468
2651
  const existing = readState2();
2469
2652
  if (existing) {
2470
2653
  const healthy = await checkHealth2(existing.port);
@@ -2505,23 +2688,23 @@ async function startIOS(opts = {}) {
2505
2688
  }
2506
2689
  }
2507
2690
  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")
2691
+ path11.resolve(__dirname_svc, "../../../browse-ios-runner"),
2692
+ path11.resolve(__dirname_svc, "../../browse-ios-runner"),
2693
+ path11.resolve(__dirname_svc, "../bin/browse-ios-runner")
2511
2694
  ];
2512
- const runnerDir = runnerCandidates.find((d) => fs10.existsSync(path10.join(d, "project.yml"))) || runnerCandidates[0];
2513
- const { execSync: execSync7 } = await import("child_process");
2514
- if (!fs10.existsSync(path10.join(runnerDir, "BrowseRunner.xcodeproj", "project.pbxproj"))) {
2695
+ const runnerDir = runnerCandidates.find((d) => fs11.existsSync(path11.join(d, "project.yml"))) || runnerCandidates[0];
2696
+ const { execSync: execSync8 } = await import("child_process");
2697
+ if (!fs11.existsSync(path11.join(runnerDir, "BrowseRunner.xcodeproj", "project.pbxproj"))) {
2515
2698
  log2("Generating Xcode project...");
2516
- execSync7("xcodegen generate --spec project.yml", { cwd: runnerDir, stdio: "pipe" });
2699
+ execSync8("xcodegen generate --spec project.yml", { cwd: runnerDir, stdio: "pipe" });
2517
2700
  }
2518
2701
  log2("Building iOS runner...");
2519
- execSync7(
2702
+ execSync8(
2520
2703
  `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`,
2521
2704
  { cwd: runnerDir, stdio: "pipe", timeout: 12e4 }
2522
2705
  );
2523
2706
  log2("Starting runner...");
2524
- const proc = (0, import_child_process7.spawn)("xcodebuild", [
2707
+ const proc = (0, import_child_process8.spawn)("xcodebuild", [
2525
2708
  "test",
2526
2709
  "-project",
2527
2710
  "BrowseRunner.xcodeproj",
@@ -2568,17 +2751,17 @@ async function startIOS(opts = {}) {
2568
2751
  writeState2(state);
2569
2752
  return state;
2570
2753
  }
2571
- var fs10, path10, import_child_process7, import_url4, __filename_svc, __dirname_svc, DEFAULT_PORT, sleep2;
2754
+ var fs11, path11, import_child_process8, import_url4, __filename_svc, __dirname_svc, DEFAULT_PORT, sleep2;
2572
2755
  var init_sim_service2 = __esm({
2573
2756
  "src/app/ios/sim-service.ts"() {
2574
2757
  "use strict";
2575
- fs10 = __toESM(require("fs"), 1);
2576
- path10 = __toESM(require("path"), 1);
2577
- import_child_process7 = require("child_process");
2758
+ fs11 = __toESM(require("fs"), 1);
2759
+ path11 = __toESM(require("path"), 1);
2760
+ import_child_process8 = require("child_process");
2578
2761
  import_url4 = require("url");
2579
2762
  init_controller();
2580
2763
  __filename_svc = (0, import_url4.fileURLToPath)(__import_meta_url);
2581
- __dirname_svc = path10.dirname(__filename_svc);
2764
+ __dirname_svc = path11.dirname(__filename_svc);
2582
2765
  DEFAULT_PORT = 9820;
2583
2766
  sleep2 = (ms) => new Promise((r2) => setTimeout(r2, ms));
2584
2767
  }
@@ -2592,11 +2775,12 @@ __export(sim_cli_exports, {
2592
2775
  async function handleSimCLI(args) {
2593
2776
  const sub = args[0];
2594
2777
  if (!sub || sub === "--help") {
2595
- console.log("Usage: browse sim start --platform ios|android [--device <name>] [--app <id>] [--visible]");
2778
+ console.log("Usage: browse sim start --platform ios|android [--device <name>] [--app <id-or-path>] [--visible]");
2596
2779
  console.log(" browse sim stop [--platform ios|android]");
2597
2780
  console.log(" browse sim status [--platform ios|android]");
2598
2781
  console.log("");
2599
2782
  console.log("Flags:");
2783
+ console.log(" --app Bundle ID, package name, or path to .app/.ipa/.apk");
2600
2784
  console.log(" --visible Open the Simulator window (default: headless/background)");
2601
2785
  return;
2602
2786
  }
@@ -2673,14 +2857,118 @@ async function handleSimCLI(args) {
2673
2857
  console.error(`Unknown platform: '${platform}'. Use ios or android.`);
2674
2858
  process.exit(1);
2675
2859
  }
2676
- return;
2677
- }
2678
- console.error(`Unknown sim subcommand: ${sub}`);
2679
- process.exit(1);
2860
+ return;
2861
+ }
2862
+ console.error(`Unknown sim subcommand: ${sub}`);
2863
+ process.exit(1);
2864
+ }
2865
+ var init_sim_cli = __esm({
2866
+ "src/sim-cli.ts"() {
2867
+ "use strict";
2868
+ }
2869
+ });
2870
+
2871
+ // src/app/macos/bridge.ts
2872
+ var bridge_exports3 = {};
2873
+ __export(bridge_exports3, {
2874
+ createMacOSBridge: () => createMacOSBridge,
2875
+ ensureMacOSBridge: () => ensureMacOSBridge,
2876
+ resolveBridgePath: () => resolveBridgePath
2877
+ });
2878
+ function resolveBridgePath() {
2879
+ const candidates = [
2880
+ // 1. Local dev build
2881
+ path12.resolve(__dirname_bridge, "../../../browse-ax/.build/release/browse-ax"),
2882
+ path12.resolve(__dirname_bridge, "../../../browse-ax/.build/debug/browse-ax"),
2883
+ // 2. Installed alongside source (bin/ at project root)
2884
+ path12.resolve(__dirname_bridge, "../../bin/browse-ax"),
2885
+ // 3. Bundled build (dist/browse.cjs → ../bin/)
2886
+ path12.resolve(__dirname_bridge, "../bin/browse-ax")
2887
+ ];
2888
+ for (const p of candidates) {
2889
+ if (fs12.existsSync(p)) return p;
2890
+ }
2891
+ const lazyPath = path12.join(
2892
+ process.env.BROWSE_LOCAL_DIR || path12.join(process.cwd(), ".browse"),
2893
+ "bin",
2894
+ "browse-ax"
2895
+ );
2896
+ if (fs12.existsSync(lazyPath)) return lazyPath;
2897
+ throw new Error(
2898
+ "browse-ax binary not found. Run: browse enable macos\nOr build manually: cd browse-ax && swift build -c release"
2899
+ );
2900
+ }
2901
+ async function ensureMacOSBridge() {
2902
+ if (process.platform !== "darwin") {
2903
+ throw new Error("App automation requires macOS (uses Accessibility API)");
2904
+ }
2905
+ return resolveBridgePath();
2906
+ }
2907
+ async function execBridge(bridgePath, args) {
2908
+ return new Promise((resolve10, reject) => {
2909
+ const proc = (0, import_child_process9.spawn)(bridgePath, args, {
2910
+ stdio: ["ignore", "pipe", "pipe"]
2911
+ });
2912
+ const chunks = [];
2913
+ const errChunks = [];
2914
+ proc.stdout.on("data", (c) => chunks.push(c));
2915
+ proc.stderr.on("data", (c) => errChunks.push(c));
2916
+ proc.on("close", (code) => {
2917
+ const stdout = Buffer.concat(chunks).toString("utf-8").trim();
2918
+ const stderr = Buffer.concat(errChunks).toString("utf-8").trim();
2919
+ if (code !== 0) {
2920
+ try {
2921
+ const err = JSON.parse(stderr || stdout);
2922
+ reject(new Error(err.error || `Bridge exited with code ${code}`));
2923
+ } catch {
2924
+ reject(new Error(stderr || `Bridge exited with code ${code}`));
2925
+ }
2926
+ return;
2927
+ }
2928
+ try {
2929
+ resolve10(JSON.parse(stdout));
2930
+ } catch {
2931
+ reject(new Error(`Invalid bridge output: ${stdout.slice(0, 200)}`));
2932
+ }
2933
+ });
2934
+ });
2935
+ }
2936
+ function createMacOSBridge(bridgePath, pid) {
2937
+ const base = ["--pid", String(pid)];
2938
+ return {
2939
+ async tree() {
2940
+ return execBridge(bridgePath, [...base, "tree"]);
2941
+ },
2942
+ async action(nodePath, actionName) {
2943
+ return execBridge(bridgePath, [...base, "action", JSON.stringify(nodePath), actionName]);
2944
+ },
2945
+ async setValue(nodePath, value) {
2946
+ return execBridge(bridgePath, [...base, "set-value", JSON.stringify(nodePath), value]);
2947
+ },
2948
+ async type(text) {
2949
+ return execBridge(bridgePath, [...base, "type", text]);
2950
+ },
2951
+ async press(key) {
2952
+ return execBridge(bridgePath, [...base, "press", key]);
2953
+ },
2954
+ async screenshot(outputPath) {
2955
+ return execBridge(bridgePath, [...base, "screenshot", outputPath]);
2956
+ },
2957
+ async state() {
2958
+ return execBridge(bridgePath, [...base, "state"]);
2959
+ }
2960
+ };
2680
2961
  }
2681
- var init_sim_cli = __esm({
2682
- "src/sim-cli.ts"() {
2962
+ var import_child_process9, path12, fs12, import_url5, __filename_bridge2, __dirname_bridge;
2963
+ var init_bridge3 = __esm({
2964
+ "src/app/macos/bridge.ts"() {
2683
2965
  "use strict";
2966
+ import_child_process9 = require("child_process");
2967
+ path12 = __toESM(require("path"), 1);
2968
+ fs12 = __toESM(require("fs"), 1);
2969
+ import_url5 = require("url");
2970
+ __filename_bridge2 = (0, import_url5.fileURLToPath)(__import_meta_url);
2971
+ __dirname_bridge = path12.dirname(__filename_bridge2);
2684
2972
  }
2685
2973
  });
2686
2974
 
@@ -2689,153 +2977,173 @@ var enable_exports = {};
2689
2977
  __export(enable_exports, {
2690
2978
  handleEnable: () => handleEnable
2691
2979
  });
2980
+ function findPath(candidates) {
2981
+ return candidates.find((p) => fs13.existsSync(p)) || null;
2982
+ }
2692
2983
  async function enableAndroid() {
2693
2984
  log("Enabling Android...");
2694
- const { ensureAndroidBridge: ensureAndroidBridge2, AdbNotFoundError: AdbNotFoundError2, installAdb: installAdb2 } = await Promise.resolve().then(() => (init_bridge(), bridge_exports));
2695
- try {
2696
- await ensureAndroidBridge2();
2697
- log("adb: already available");
2698
- } catch (err) {
2699
- if (err instanceof AdbNotFoundError2) {
2700
- const ok = await installAdb2(log);
2701
- if (!ok) throw new Error("Failed to install adb");
2702
- }
2985
+ const prebuiltApk = findPath([
2986
+ path13.resolve(__dirname_enable, "../bin/browse-android.apk"),
2987
+ path13.resolve(__dirname_enable, "../../bin/browse-android.apk"),
2988
+ path13.resolve(__dirname_enable, "browse-android.apk")
2989
+ ]);
2990
+ if (prebuiltApk) {
2991
+ log(`Driver APK: ${prebuiltApk}`);
2992
+ log("Android ready (pre-built).");
2993
+ return;
2703
2994
  }
2704
- const { ensureJavaHome: ensureJavaHome2, findSdkRoot: findSdkRoot2, installSdk: installSdk2, installSystemImage: installSystemImage2, createAvd: createAvd2 } = await Promise.resolve().then(() => (init_emulator(), emulator_exports));
2705
- ensureJavaHome2();
2995
+ log("Pre-built APK not found, building from source...");
2996
+ const { ensureJavaHome: ensureJavaHome2, findSdkRoot: findSdkRoot2, installSdk: installSdk2, createAvd: createAvd2 } = await Promise.resolve().then(() => (init_emulator(), emulator_exports));
2997
+ const { installAdb: installAdb2 } = await Promise.resolve().then(() => (init_bridge(), bridge_exports));
2706
2998
  try {
2707
- (0, import_child_process8.execSync)("java -version", { stdio: "ignore", timeout: 5e3 });
2708
- log("Java: available");
2999
+ (0, import_child_process10.execSync)("adb version", { stdio: "ignore", timeout: 5e3 });
3000
+ log("adb: available");
2709
3001
  } catch {
2710
- log("Java not found. Run: brew install openjdk@21");
3002
+ log("Installing adb...");
3003
+ await installAdb2(log);
3004
+ }
3005
+ ensureJavaHome2();
3006
+ const hasJava = () => {
3007
+ try {
3008
+ (0, import_child_process10.execSync)("java -version", { stdio: "ignore", timeout: 5e3 });
3009
+ return true;
3010
+ } catch {
3011
+ return false;
3012
+ }
3013
+ };
3014
+ if (!hasJava() && process.platform === "darwin") {
3015
+ log("Installing Java (JDK 21)...");
3016
+ try {
3017
+ (0, import_child_process10.execSync)("brew install openjdk@21", { stdio: ["ignore", "pipe", "pipe"], timeout: 3e5 });
3018
+ } catch {
3019
+ }
3020
+ ensureJavaHome2();
2711
3021
  }
3022
+ if (!hasJava()) throw new Error("Java not found. Install: brew install openjdk@21");
3023
+ log("Java: available");
2712
3024
  let sdkRoot = findSdkRoot2();
2713
- if (!sdkRoot) {
2714
- sdkRoot = await installSdk2(log);
2715
- }
2716
- if (sdkRoot) {
2717
- log(`SDK: ${sdkRoot}`);
2718
- const sdkMgr = [
2719
- path11.join(sdkRoot, "cmdline-tools/latest/bin/sdkmanager"),
2720
- path11.join(sdkRoot, "bin/sdkmanager")
2721
- ].find((p) => fs11.existsSync(p));
2722
- if (sdkMgr) {
3025
+ if (!sdkRoot) sdkRoot = await installSdk2(log);
3026
+ if (!sdkRoot) throw new Error("Android SDK not found.");
3027
+ log(`SDK: ${sdkRoot}`);
3028
+ const sdkMgr = findPath([
3029
+ path13.join(sdkRoot, "cmdline-tools/latest/bin/sdkmanager"),
3030
+ path13.join(sdkRoot, "bin/sdkmanager")
3031
+ ]);
3032
+ if (sdkMgr) {
3033
+ try {
3034
+ (0, import_child_process10.execSync)(`yes | "${sdkMgr}" --licenses`, { stdio: "ignore", timeout: 6e4, shell: "/bin/bash" });
3035
+ } catch {
3036
+ }
3037
+ for (const comp of ["emulator", "platform-tools", "platforms;android-35", "build-tools;35.0.0", "system-images;android-35;google_apis;arm64-v8a"]) {
2723
3038
  try {
2724
- (0, import_child_process8.execSync)(`yes | "${sdkMgr}" --licenses`, { stdio: "ignore", timeout: 6e4, shell: "/bin/bash" });
3039
+ (0, import_child_process10.execSync)(`"${sdkMgr}" --install "${comp}"`, { stdio: "ignore", timeout: 3e5 });
2725
3040
  } catch {
2726
3041
  }
2727
- const components = ["emulator", "platform-tools", "platforms;android-35", "build-tools;35.0.0", "system-images;android-35;google_apis;arm64-v8a"];
2728
- for (const comp of components) {
2729
- try {
2730
- (0, import_child_process8.execSync)(`"${sdkMgr}" --install "${comp}"`, { stdio: "ignore", timeout: 3e5 });
2731
- } catch {
2732
- }
2733
- }
2734
- log("SDK components: installed");
2735
- const avdMgr = [
2736
- path11.join(sdkRoot, "cmdline-tools/latest/bin/avdmanager"),
2737
- path11.join(sdkRoot, "bin/avdmanager")
2738
- ].find((p) => fs11.existsSync(p));
2739
- if (avdMgr) {
2740
- try {
2741
- const avds = (0, import_child_process8.execSync)(`"${avdMgr}" list avd -c`, { encoding: "utf-8", timeout: 1e4, stdio: ["ignore", "pipe", "pipe"] }).trim();
2742
- if (!avds.includes("browse_default")) {
2743
- createAvd2(sdkRoot, avdMgr, log);
2744
- } else {
2745
- log("AVD browse_default: exists");
2746
- }
2747
- } catch {
2748
- }
3042
+ }
3043
+ log("SDK components: installed");
3044
+ }
3045
+ const avdMgr = findPath([
3046
+ path13.join(sdkRoot, "cmdline-tools/latest/bin/avdmanager"),
3047
+ path13.join(sdkRoot, "bin/avdmanager")
3048
+ ]);
3049
+ if (avdMgr) {
3050
+ try {
3051
+ const avds = (0, import_child_process10.execSync)(`"${avdMgr}" list avd -c`, { encoding: "utf-8", timeout: 1e4, stdio: ["ignore", "pipe", "pipe"] }).trim();
3052
+ if (!avds.includes("browse_default")) {
3053
+ createAvd2(sdkRoot, avdMgr, log);
3054
+ } else {
3055
+ log("AVD: browse_default exists");
2749
3056
  }
3057
+ } catch {
2750
3058
  }
2751
3059
  }
2752
- const driverDir = path11.resolve(__dirname_enable, "../browse-android");
2753
- const apkPath = path11.join(driverDir, "app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk");
2754
- if (!fs11.existsSync(apkPath) && fs11.existsSync(path11.join(driverDir, "gradlew"))) {
2755
- const { findSdkRoot: findSdkRoot3, ensureJavaHome: ensureJavaHome3 } = await Promise.resolve().then(() => (init_emulator(), emulator_exports));
2756
- ensureJavaHome3();
2757
- const sdkRoot2 = findSdkRoot3();
2758
- if (sdkRoot2) process.env.ANDROID_HOME = sdkRoot2;
3060
+ const driverDir = findPath([
3061
+ path13.resolve(__dirname_enable, "../browse-android"),
3062
+ path13.resolve(__dirname_enable, "../../browse-android")
3063
+ ].filter((d) => fs13.existsSync(path13.join(d, "gradlew"))));
3064
+ if (driverDir) {
3065
+ if (!process.env.ANDROID_HOME) process.env.ANDROID_HOME = sdkRoot;
2759
3066
  log("Building Android driver APK...");
2760
3067
  try {
2761
- (0, import_child_process8.execSync)("./gradlew :app:assembleDebug :app:assembleDebugAndroidTest --no-daemon", {
3068
+ (0, import_child_process10.execSync)("./gradlew :app:assembleDebug :app:assembleDebugAndroidTest --no-daemon", {
2762
3069
  cwd: driverDir,
2763
3070
  stdio: ["ignore", "pipe", "pipe"],
2764
3071
  timeout: 3e5
2765
3072
  });
2766
- log("Android driver APK built.");
3073
+ log("APK built.");
2767
3074
  } catch (err) {
2768
3075
  log(`APK build failed: ${err.message?.split("\n")[0]}`);
2769
3076
  }
2770
- } else if (fs11.existsSync(apkPath)) {
2771
- log("Android driver APK: already built");
2772
3077
  }
2773
3078
  log("Android enabled.");
2774
3079
  }
2775
3080
  async function enableIOS() {
2776
3081
  log("Enabling iOS...");
2777
- if (process.platform !== "darwin") {
2778
- throw new Error("iOS requires macOS with Xcode installed.");
2779
- }
3082
+ if (process.platform !== "darwin") throw new Error("iOS requires macOS with Xcode.");
2780
3083
  try {
2781
- (0, import_child_process8.execSync)("xcodebuild -version", { stdio: "pipe", timeout: 1e4 });
3084
+ (0, import_child_process10.execSync)("xcodebuild -version", { stdio: "pipe", timeout: 1e4 });
2782
3085
  log("Xcode: available");
2783
3086
  } catch {
2784
- throw new Error("Xcode not found. Install from the App Store or: xcode-select --install");
3087
+ throw new Error("Xcode not found. Install from the App Store.");
2785
3088
  }
2786
3089
  try {
2787
- (0, import_child_process8.execSync)("which xcodegen", { stdio: "pipe", timeout: 5e3 });
2788
- log("xcodegen: available");
3090
+ (0, import_child_process10.execSync)("which xcodegen", { stdio: "pipe", timeout: 5e3 });
2789
3091
  } catch {
2790
3092
  log("Installing xcodegen...");
2791
3093
  try {
2792
- (0, import_child_process8.execSync)("brew install xcodegen", { stdio: ["ignore", "pipe", "pipe"], timeout: 12e4 });
3094
+ (0, import_child_process10.execSync)("brew install xcodegen", { stdio: ["ignore", "pipe", "pipe"], timeout: 12e4 });
2793
3095
  } catch {
2794
3096
  throw new Error("xcodegen not found. Install: brew install xcodegen");
2795
3097
  }
2796
3098
  }
2797
- const runnerDir = path11.resolve(__dirname_enable, "../browse-ios-runner");
2798
- if (fs11.existsSync(path11.join(runnerDir, "project.yml"))) {
2799
- if (!fs11.existsSync(path11.join(runnerDir, "BrowseRunner.xcodeproj", "project.pbxproj"))) {
2800
- log("Generating Xcode project...");
2801
- (0, import_child_process8.execSync)("xcodegen generate --spec project.yml", { cwd: runnerDir, stdio: "pipe" });
2802
- }
2803
- const { resolveSimulator: resolveSimulator2 } = await Promise.resolve().then(() => (init_controller(), controller_exports));
2804
- let udid;
2805
- try {
2806
- const sim = await resolveSimulator2();
2807
- udid = sim.udid;
2808
- } catch {
2809
- log("No iOS Simulator found. Skipping pre-build (will build on first sim start).");
2810
- log("iOS enabled (source ready).");
2811
- return;
2812
- }
3099
+ log("xcodegen: available");
3100
+ const runnerDir = findPath([
3101
+ path13.resolve(__dirname_enable, "../browse-ios-runner"),
3102
+ path13.resolve(__dirname_enable, "../../browse-ios-runner"),
3103
+ path13.resolve(__dirname_enable, "../bin/browse-ios-runner")
3104
+ ].filter((d) => fs13.existsSync(path13.join(d, "project.yml"))));
3105
+ if (!runnerDir) {
3106
+ throw new Error("iOS runner source not found. Reinstall: npm install -g @ulpi/browse");
3107
+ }
3108
+ if (!fs13.existsSync(path13.join(runnerDir, "BrowseRunner.xcodeproj", "project.pbxproj"))) {
3109
+ log("Generating Xcode project...");
3110
+ (0, import_child_process10.execSync)("xcodegen generate --spec project.yml", { cwd: runnerDir, stdio: "pipe" });
3111
+ }
3112
+ const { resolveSimulator: resolveSimulator2 } = await Promise.resolve().then(() => (init_controller(), controller_exports));
3113
+ try {
3114
+ const sim = await resolveSimulator2();
2813
3115
  log("Building iOS runner...");
2814
- (0, import_child_process8.execSync)(
2815
- `xcodebuild build-for-testing -project BrowseRunner.xcodeproj -scheme BrowseRunnerApp -sdk iphonesimulator -destination "id=${udid}" -derivedDataPath .build CODE_SIGN_IDENTITY="" CODE_SIGNING_ALLOWED=NO -quiet`,
3116
+ (0, import_child_process10.execSync)(
3117
+ `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`,
2816
3118
  { cwd: runnerDir, stdio: "pipe", timeout: 12e4 }
2817
3119
  );
2818
3120
  log("iOS runner built.");
3121
+ } catch {
3122
+ log("No simulator found. Runner will build on first sim start.");
2819
3123
  }
2820
3124
  log("iOS enabled.");
2821
3125
  }
2822
3126
  async function enableMacOS() {
2823
3127
  log("Enabling macOS...");
2824
- if (process.platform !== "darwin") {
2825
- throw new Error("macOS app automation requires macOS.");
3128
+ if (process.platform !== "darwin") throw new Error("macOS app automation requires macOS.");
3129
+ const { resolveBridgePath: resolveBridgePath2 } = await Promise.resolve().then(() => (init_bridge3(), bridge_exports3));
3130
+ try {
3131
+ const existing = resolveBridgePath2();
3132
+ log(`browse-ax: ${existing}`);
3133
+ log("macOS enabled.");
3134
+ return;
3135
+ } catch {
2826
3136
  }
2827
- const axDir = path11.resolve(__dirname_enable, "../browse-ax");
2828
- const axBin = path11.join(axDir, ".build/release/browse-ax");
2829
- if (fs11.existsSync(path11.join(axDir, "Package.swift"))) {
2830
- if (fs11.existsSync(axBin)) {
2831
- log("browse-ax: already built");
2832
- } else {
2833
- log("Building browse-ax...");
2834
- (0, import_child_process8.execSync)("swift build -c release", { cwd: axDir, stdio: ["ignore", "pipe", "pipe"], timeout: 12e4 });
2835
- log("browse-ax built.");
2836
- }
3137
+ const axDir = findPath([
3138
+ path13.resolve(__dirname_enable, "../browse-ax"),
3139
+ path13.resolve(__dirname_enable, "../../browse-ax")
3140
+ ].filter((d) => fs13.existsSync(path13.join(d, "Package.swift"))));
3141
+ if (axDir) {
3142
+ log("Building browse-ax...");
3143
+ (0, import_child_process10.execSync)("swift build -c release", { cwd: axDir, stdio: ["ignore", "pipe", "pipe"], timeout: 12e4 });
3144
+ log("browse-ax built.");
2837
3145
  } else {
2838
- throw new Error("browse-ax source not found.");
3146
+ throw new Error("browse-ax not found. Reinstall: npm install -g @ulpi/browse");
2839
3147
  }
2840
3148
  log("macOS enabled.");
2841
3149
  }
@@ -2844,8 +3152,8 @@ async function handleEnable(args) {
2844
3152
  if (!platform || platform === "--help") {
2845
3153
  console.log("Usage: browse enable android|ios|macos|all");
2846
3154
  console.log("");
2847
- console.log("Downloads dependencies and builds native drivers for each platform.");
2848
- console.log("Run once \u2014 everything is cached for future use.");
3155
+ console.log("Verifies native drivers are ready. Pre-built binaries ship with npm install.");
3156
+ console.log("If missing (building from source), installs dependencies and builds them.");
2849
3157
  return;
2850
3158
  }
2851
3159
  const platforms = platform === "all" ? ["android", "ios", "macos"] : [platform];
@@ -2872,15 +3180,15 @@ async function handleEnable(args) {
2872
3180
  console.log("");
2873
3181
  }
2874
3182
  }
2875
- var import_child_process8, fs11, path11, import_url5, __dirname_enable, log;
3183
+ var import_child_process10, fs13, path13, import_url6, __dirname_enable, log;
2876
3184
  var init_enable = __esm({
2877
3185
  "src/enable.ts"() {
2878
3186
  "use strict";
2879
- import_child_process8 = require("child_process");
2880
- fs11 = __toESM(require("fs"), 1);
2881
- path11 = __toESM(require("path"), 1);
2882
- import_url5 = require("url");
2883
- __dirname_enable = path11.dirname((0, import_url5.fileURLToPath)(__import_meta_url));
3187
+ import_child_process10 = require("child_process");
3188
+ fs13 = __toESM(require("fs"), 1);
3189
+ path13 = __toESM(require("path"), 1);
3190
+ import_url6 = require("url");
3191
+ __dirname_enable = path13.dirname((0, import_url6.fileURLToPath)(__import_meta_url));
2884
3192
  log = (msg) => process.stderr.write(`[browse] ${msg}
2885
3193
  `);
2886
3194
  }
@@ -3174,8 +3482,8 @@ async function handleReadCommand(command, args, bm, buffers) {
3174
3482
  case "eval": {
3175
3483
  const filePath = args[0];
3176
3484
  if (!filePath) throw new Error("Usage: browse eval <js-file>");
3177
- if (!fs12.existsSync(filePath)) throw new Error(`File not found: ${filePath}`);
3178
- const code = fs12.readFileSync(filePath, "utf-8");
3485
+ if (!fs14.existsSync(filePath)) throw new Error(`File not found: ${filePath}`);
3486
+ const code = fs14.readFileSync(filePath, "utf-8");
3179
3487
  const result = await evalCtx.evaluate(code);
3180
3488
  return typeof result === "object" ? JSON.stringify(result, null, 2) : String(result ?? "");
3181
3489
  }
@@ -3515,13 +3823,13 @@ function registerReadDefinitions(registry4) {
3515
3823
  });
3516
3824
  }
3517
3825
  }
3518
- var fs12;
3826
+ var fs14;
3519
3827
  var init_read = __esm({
3520
3828
  "src/commands/read.ts"() {
3521
3829
  "use strict";
3522
3830
  init_emulation();
3523
3831
  init_constants();
3524
- fs12 = __toESM(require("fs"), 1);
3832
+ fs14 = __toESM(require("fs"), 1);
3525
3833
  }
3526
3834
  });
3527
3835
 
@@ -4168,7 +4476,7 @@ async function handleWriteCommand(command, args, bm, domainFilter) {
4168
4476
  const elapsed = Date.now() - start2;
4169
4477
  return `Request matched: ${match.description} (${elapsed}ms)`;
4170
4478
  }
4171
- await new Promise((resolve9) => setTimeout(resolve9, 100));
4479
+ await new Promise((resolve10) => setTimeout(resolve10, 100));
4172
4480
  }
4173
4481
  const finalMatch = matchNetworkRequest2(cond, buffers);
4174
4482
  const { method, pattern } = parseRequestValue2(requestPattern);
@@ -4213,14 +4521,14 @@ async function handleWriteCommand(command, args, bm, domainFilter) {
4213
4521
  const file = args[1];
4214
4522
  if (!file) throw new Error("Usage: browse cookie export <file>");
4215
4523
  const cookies = await page.context().cookies();
4216
- fs13.writeFileSync(file, JSON.stringify(cookies, null, 2));
4524
+ fs15.writeFileSync(file, JSON.stringify(cookies, null, 2));
4217
4525
  return `Exported ${cookies.length} cookie(s) to ${file}`;
4218
4526
  }
4219
4527
  if (cookieStr === "import") {
4220
4528
  const file = args[1];
4221
4529
  if (!file) throw new Error("Usage: browse cookie import <file>");
4222
- if (!fs13.existsSync(file)) throw new Error(`File not found: ${file}`);
4223
- const cookies = JSON.parse(fs13.readFileSync(file, "utf-8"));
4530
+ if (!fs15.existsSync(file)) throw new Error(`File not found: ${file}`);
4531
+ const cookies = JSON.parse(fs15.readFileSync(file, "utf-8"));
4224
4532
  if (!Array.isArray(cookies)) throw new Error("Cookie file must contain a JSON array of cookie objects");
4225
4533
  await page.context().addCookies(cookies);
4226
4534
  return `Imported ${cookies.length} cookie(s) from ${file}`;
@@ -4287,7 +4595,7 @@ Note: Cookies and tab URLs preserved. localStorage/sessionStorage were reset (Pl
4287
4595
  const [selector, ...filePaths] = args;
4288
4596
  if (!selector || filePaths.length === 0) throw new Error("Usage: browse upload <selector> <file1> [file2] ...");
4289
4597
  for (const fp of filePaths) {
4290
- if (!fs13.existsSync(fp)) throw new Error(`File not found: ${fp}`);
4598
+ if (!fs15.existsSync(fp)) throw new Error(`File not found: ${fp}`);
4291
4599
  }
4292
4600
  const resolved = bm.resolveRef(selector);
4293
4601
  if ("locator" in resolved) {
@@ -4747,13 +5055,13 @@ function registerWriteDefinitions(registry4) {
4747
5055
  });
4748
5056
  }
4749
5057
  }
4750
- var fs13;
5058
+ var fs15;
4751
5059
  var init_write = __esm({
4752
5060
  "src/commands/write.ts"() {
4753
5061
  "use strict";
4754
5062
  init_emulation();
4755
5063
  init_constants();
4756
- fs13 = __toESM(require("fs"), 1);
5064
+ fs15 = __toESM(require("fs"), 1);
4757
5065
  }
4758
5066
  });
4759
5067
 
@@ -5110,9 +5418,9 @@ ${legend.join("\n")}`;
5110
5418
  try {
5111
5419
  for (const vp of viewports) {
5112
5420
  await page.setViewportSize({ width: vp.width, height: vp.height });
5113
- const path24 = `${prefix}-${vp.name}.png`;
5114
- await page.screenshot({ path: path24, fullPage: true });
5115
- results.push(`${vp.name} (${vp.width}x${vp.height}): ${path24}`);
5421
+ const path25 = `${prefix}-${vp.name}.png`;
5422
+ await page.screenshot({ path: path25, fullPage: true });
5423
+ results.push(`${vp.name} (${vp.width}x${vp.height}): ${path25}`);
5116
5424
  }
5117
5425
  } finally {
5118
5426
  if (originalViewport) {
@@ -5127,13 +5435,13 @@ ${legend.join("\n")}`;
5127
5435
  const diffArgs = args.filter((a) => a !== "--full");
5128
5436
  const baseline = diffArgs[0];
5129
5437
  if (!baseline) throw new Error("Usage: browse screenshot-diff <baseline> [current] [--threshold 0.1] [--full]");
5130
- if (!fs14.existsSync(baseline)) throw new Error(`Baseline file not found: ${baseline}`);
5438
+ if (!fs16.existsSync(baseline)) throw new Error(`Baseline file not found: ${baseline}`);
5131
5439
  let thresholdPct = 0.1;
5132
5440
  const threshIdx = diffArgs.indexOf("--threshold");
5133
5441
  if (threshIdx !== -1 && diffArgs[threshIdx + 1]) {
5134
5442
  thresholdPct = parseFloat(diffArgs[threshIdx + 1]);
5135
5443
  }
5136
- const baselineBuffer = fs14.readFileSync(baseline);
5444
+ const baselineBuffer = fs16.readFileSync(baseline);
5137
5445
  let currentBuffer;
5138
5446
  let currentPath;
5139
5447
  for (let i = 1; i < diffArgs.length; i++) {
@@ -5147,8 +5455,8 @@ ${legend.join("\n")}`;
5147
5455
  }
5148
5456
  }
5149
5457
  if (currentPath) {
5150
- if (!fs14.existsSync(currentPath)) throw new Error(`Current screenshot not found: ${currentPath}`);
5151
- currentBuffer = fs14.readFileSync(currentPath);
5458
+ if (!fs16.existsSync(currentPath)) throw new Error(`Current screenshot not found: ${currentPath}`);
5459
+ currentBuffer = fs16.readFileSync(currentPath);
5152
5460
  } else {
5153
5461
  const page = bm.getPage();
5154
5462
  currentBuffer = await page.screenshot({ fullPage: isFullPageDiff });
@@ -5158,7 +5466,7 @@ ${legend.join("\n")}`;
5158
5466
  const extIdx = baseline.lastIndexOf(".");
5159
5467
  const diffPath = extIdx > 0 ? baseline.slice(0, extIdx) + "-diff" + baseline.slice(extIdx) : baseline + "-diff.png";
5160
5468
  if (!result.passed && result.diffImage) {
5161
- fs14.writeFileSync(diffPath, result.diffImage);
5469
+ fs16.writeFileSync(diffPath, result.diffImage);
5162
5470
  }
5163
5471
  return [
5164
5472
  `Pixels: ${result.totalPixels}`,
@@ -5173,11 +5481,11 @@ ${legend.join("\n")}`;
5173
5481
  throw new Error(`Unknown screenshots command: ${command}`);
5174
5482
  }
5175
5483
  }
5176
- var fs14, LOCAL_DIR;
5484
+ var fs16, LOCAL_DIR;
5177
5485
  var init_screenshots = __esm({
5178
5486
  "src/commands/meta/screenshots.ts"() {
5179
5487
  "use strict";
5180
- fs14 = __toESM(require("fs"), 1);
5488
+ fs16 = __toESM(require("fs"), 1);
5181
5489
  LOCAL_DIR = process.env.BROWSE_LOCAL_DIR || "/tmp";
5182
5490
  }
5183
5491
  });
@@ -5259,17 +5567,17 @@ var require_visit = __commonJS({
5259
5567
  visit.BREAK = BREAK;
5260
5568
  visit.SKIP = SKIP;
5261
5569
  visit.REMOVE = REMOVE;
5262
- function visit_(key, node, visitor, path24) {
5263
- const ctrl = callVisitor(key, node, visitor, path24);
5570
+ function visit_(key, node, visitor, path25) {
5571
+ const ctrl = callVisitor(key, node, visitor, path25);
5264
5572
  if (identity.isNode(ctrl) || identity.isPair(ctrl)) {
5265
- replaceNode(key, path24, ctrl);
5266
- return visit_(key, ctrl, visitor, path24);
5573
+ replaceNode(key, path25, ctrl);
5574
+ return visit_(key, ctrl, visitor, path25);
5267
5575
  }
5268
5576
  if (typeof ctrl !== "symbol") {
5269
5577
  if (identity.isCollection(node)) {
5270
- path24 = Object.freeze(path24.concat(node));
5578
+ path25 = Object.freeze(path25.concat(node));
5271
5579
  for (let i = 0; i < node.items.length; ++i) {
5272
- const ci = visit_(i, node.items[i], visitor, path24);
5580
+ const ci = visit_(i, node.items[i], visitor, path25);
5273
5581
  if (typeof ci === "number")
5274
5582
  i = ci - 1;
5275
5583
  else if (ci === BREAK)
@@ -5280,13 +5588,13 @@ var require_visit = __commonJS({
5280
5588
  }
5281
5589
  }
5282
5590
  } else if (identity.isPair(node)) {
5283
- path24 = Object.freeze(path24.concat(node));
5284
- const ck = visit_("key", node.key, visitor, path24);
5591
+ path25 = Object.freeze(path25.concat(node));
5592
+ const ck = visit_("key", node.key, visitor, path25);
5285
5593
  if (ck === BREAK)
5286
5594
  return BREAK;
5287
5595
  else if (ck === REMOVE)
5288
5596
  node.key = null;
5289
- const cv = visit_("value", node.value, visitor, path24);
5597
+ const cv = visit_("value", node.value, visitor, path25);
5290
5598
  if (cv === BREAK)
5291
5599
  return BREAK;
5292
5600
  else if (cv === REMOVE)
@@ -5307,17 +5615,17 @@ var require_visit = __commonJS({
5307
5615
  visitAsync.BREAK = BREAK;
5308
5616
  visitAsync.SKIP = SKIP;
5309
5617
  visitAsync.REMOVE = REMOVE;
5310
- async function visitAsync_(key, node, visitor, path24) {
5311
- const ctrl = await callVisitor(key, node, visitor, path24);
5618
+ async function visitAsync_(key, node, visitor, path25) {
5619
+ const ctrl = await callVisitor(key, node, visitor, path25);
5312
5620
  if (identity.isNode(ctrl) || identity.isPair(ctrl)) {
5313
- replaceNode(key, path24, ctrl);
5314
- return visitAsync_(key, ctrl, visitor, path24);
5621
+ replaceNode(key, path25, ctrl);
5622
+ return visitAsync_(key, ctrl, visitor, path25);
5315
5623
  }
5316
5624
  if (typeof ctrl !== "symbol") {
5317
5625
  if (identity.isCollection(node)) {
5318
- path24 = Object.freeze(path24.concat(node));
5626
+ path25 = Object.freeze(path25.concat(node));
5319
5627
  for (let i = 0; i < node.items.length; ++i) {
5320
- const ci = await visitAsync_(i, node.items[i], visitor, path24);
5628
+ const ci = await visitAsync_(i, node.items[i], visitor, path25);
5321
5629
  if (typeof ci === "number")
5322
5630
  i = ci - 1;
5323
5631
  else if (ci === BREAK)
@@ -5328,13 +5636,13 @@ var require_visit = __commonJS({
5328
5636
  }
5329
5637
  }
5330
5638
  } else if (identity.isPair(node)) {
5331
- path24 = Object.freeze(path24.concat(node));
5332
- const ck = await visitAsync_("key", node.key, visitor, path24);
5639
+ path25 = Object.freeze(path25.concat(node));
5640
+ const ck = await visitAsync_("key", node.key, visitor, path25);
5333
5641
  if (ck === BREAK)
5334
5642
  return BREAK;
5335
5643
  else if (ck === REMOVE)
5336
5644
  node.key = null;
5337
- const cv = await visitAsync_("value", node.value, visitor, path24);
5645
+ const cv = await visitAsync_("value", node.value, visitor, path25);
5338
5646
  if (cv === BREAK)
5339
5647
  return BREAK;
5340
5648
  else if (cv === REMOVE)
@@ -5361,23 +5669,23 @@ var require_visit = __commonJS({
5361
5669
  }
5362
5670
  return visitor;
5363
5671
  }
5364
- function callVisitor(key, node, visitor, path24) {
5672
+ function callVisitor(key, node, visitor, path25) {
5365
5673
  if (typeof visitor === "function")
5366
- return visitor(key, node, path24);
5674
+ return visitor(key, node, path25);
5367
5675
  if (identity.isMap(node))
5368
- return visitor.Map?.(key, node, path24);
5676
+ return visitor.Map?.(key, node, path25);
5369
5677
  if (identity.isSeq(node))
5370
- return visitor.Seq?.(key, node, path24);
5678
+ return visitor.Seq?.(key, node, path25);
5371
5679
  if (identity.isPair(node))
5372
- return visitor.Pair?.(key, node, path24);
5680
+ return visitor.Pair?.(key, node, path25);
5373
5681
  if (identity.isScalar(node))
5374
- return visitor.Scalar?.(key, node, path24);
5682
+ return visitor.Scalar?.(key, node, path25);
5375
5683
  if (identity.isAlias(node))
5376
- return visitor.Alias?.(key, node, path24);
5684
+ return visitor.Alias?.(key, node, path25);
5377
5685
  return void 0;
5378
5686
  }
5379
- function replaceNode(key, path24, node) {
5380
- const parent = path24[path24.length - 1];
5687
+ function replaceNode(key, path25, node) {
5688
+ const parent = path25[path25.length - 1];
5381
5689
  if (identity.isCollection(parent)) {
5382
5690
  parent.items[key] = node;
5383
5691
  } else if (identity.isPair(parent)) {
@@ -5985,10 +6293,10 @@ var require_Collection = __commonJS({
5985
6293
  var createNode = require_createNode();
5986
6294
  var identity = require_identity();
5987
6295
  var Node = require_Node();
5988
- function collectionFromPath(schema, path24, value) {
6296
+ function collectionFromPath(schema, path25, value) {
5989
6297
  let v = value;
5990
- for (let i = path24.length - 1; i >= 0; --i) {
5991
- const k = path24[i];
6298
+ for (let i = path25.length - 1; i >= 0; --i) {
6299
+ const k = path25[i];
5992
6300
  if (typeof k === "number" && Number.isInteger(k) && k >= 0) {
5993
6301
  const a = [];
5994
6302
  a[k] = v;
@@ -6007,7 +6315,7 @@ var require_Collection = __commonJS({
6007
6315
  sourceObjects: /* @__PURE__ */ new Map()
6008
6316
  });
6009
6317
  }
6010
- var isEmptyPath = (path24) => path24 == null || typeof path24 === "object" && !!path24[Symbol.iterator]().next().done;
6318
+ var isEmptyPath = (path25) => path25 == null || typeof path25 === "object" && !!path25[Symbol.iterator]().next().done;
6011
6319
  var Collection = class extends Node.NodeBase {
6012
6320
  constructor(type, schema) {
6013
6321
  super(type);
@@ -6037,11 +6345,11 @@ var require_Collection = __commonJS({
6037
6345
  * be a Pair instance or a `{ key, value }` object, which may not have a key
6038
6346
  * that already exists in the map.
6039
6347
  */
6040
- addIn(path24, value) {
6041
- if (isEmptyPath(path24))
6348
+ addIn(path25, value) {
6349
+ if (isEmptyPath(path25))
6042
6350
  this.add(value);
6043
6351
  else {
6044
- const [key, ...rest] = path24;
6352
+ const [key, ...rest] = path25;
6045
6353
  const node = this.get(key, true);
6046
6354
  if (identity.isCollection(node))
6047
6355
  node.addIn(rest, value);
@@ -6055,8 +6363,8 @@ var require_Collection = __commonJS({
6055
6363
  * Removes a value from the collection.
6056
6364
  * @returns `true` if the item was found and removed.
6057
6365
  */
6058
- deleteIn(path24) {
6059
- const [key, ...rest] = path24;
6366
+ deleteIn(path25) {
6367
+ const [key, ...rest] = path25;
6060
6368
  if (rest.length === 0)
6061
6369
  return this.delete(key);
6062
6370
  const node = this.get(key, true);
@@ -6070,8 +6378,8 @@ var require_Collection = __commonJS({
6070
6378
  * scalar values from their surrounding node; to disable set `keepScalar` to
6071
6379
  * `true` (collections are always returned intact).
6072
6380
  */
6073
- getIn(path24, keepScalar) {
6074
- const [key, ...rest] = path24;
6381
+ getIn(path25, keepScalar) {
6382
+ const [key, ...rest] = path25;
6075
6383
  const node = this.get(key, true);
6076
6384
  if (rest.length === 0)
6077
6385
  return !keepScalar && identity.isScalar(node) ? node.value : node;
@@ -6089,8 +6397,8 @@ var require_Collection = __commonJS({
6089
6397
  /**
6090
6398
  * Checks if the collection includes a value with the key `key`.
6091
6399
  */
6092
- hasIn(path24) {
6093
- const [key, ...rest] = path24;
6400
+ hasIn(path25) {
6401
+ const [key, ...rest] = path25;
6094
6402
  if (rest.length === 0)
6095
6403
  return this.has(key);
6096
6404
  const node = this.get(key, true);
@@ -6100,8 +6408,8 @@ var require_Collection = __commonJS({
6100
6408
  * Sets a value in this collection. For `!!set`, `value` needs to be a
6101
6409
  * boolean to add/remove the item from the set.
6102
6410
  */
6103
- setIn(path24, value) {
6104
- const [key, ...rest] = path24;
6411
+ setIn(path25, value) {
6412
+ const [key, ...rest] = path25;
6105
6413
  if (rest.length === 0) {
6106
6414
  this.set(key, value);
6107
6415
  } else {
@@ -8613,9 +8921,9 @@ var require_Document = __commonJS({
8613
8921
  this.contents.add(value);
8614
8922
  }
8615
8923
  /** Adds a value to the document. */
8616
- addIn(path24, value) {
8924
+ addIn(path25, value) {
8617
8925
  if (assertCollection(this.contents))
8618
- this.contents.addIn(path24, value);
8926
+ this.contents.addIn(path25, value);
8619
8927
  }
8620
8928
  /**
8621
8929
  * Create a new `Alias` node, ensuring that the target `node` has the required anchor.
@@ -8690,14 +8998,14 @@ var require_Document = __commonJS({
8690
8998
  * Removes a value from the document.
8691
8999
  * @returns `true` if the item was found and removed.
8692
9000
  */
8693
- deleteIn(path24) {
8694
- if (Collection.isEmptyPath(path24)) {
9001
+ deleteIn(path25) {
9002
+ if (Collection.isEmptyPath(path25)) {
8695
9003
  if (this.contents == null)
8696
9004
  return false;
8697
9005
  this.contents = null;
8698
9006
  return true;
8699
9007
  }
8700
- return assertCollection(this.contents) ? this.contents.deleteIn(path24) : false;
9008
+ return assertCollection(this.contents) ? this.contents.deleteIn(path25) : false;
8701
9009
  }
8702
9010
  /**
8703
9011
  * Returns item at `key`, or `undefined` if not found. By default unwraps
@@ -8712,10 +9020,10 @@ var require_Document = __commonJS({
8712
9020
  * scalar values from their surrounding node; to disable set `keepScalar` to
8713
9021
  * `true` (collections are always returned intact).
8714
9022
  */
8715
- getIn(path24, keepScalar) {
8716
- if (Collection.isEmptyPath(path24))
9023
+ getIn(path25, keepScalar) {
9024
+ if (Collection.isEmptyPath(path25))
8717
9025
  return !keepScalar && identity.isScalar(this.contents) ? this.contents.value : this.contents;
8718
- return identity.isCollection(this.contents) ? this.contents.getIn(path24, keepScalar) : void 0;
9026
+ return identity.isCollection(this.contents) ? this.contents.getIn(path25, keepScalar) : void 0;
8719
9027
  }
8720
9028
  /**
8721
9029
  * Checks if the document includes a value with the key `key`.
@@ -8726,10 +9034,10 @@ var require_Document = __commonJS({
8726
9034
  /**
8727
9035
  * Checks if the document includes a value at `path`.
8728
9036
  */
8729
- hasIn(path24) {
8730
- if (Collection.isEmptyPath(path24))
9037
+ hasIn(path25) {
9038
+ if (Collection.isEmptyPath(path25))
8731
9039
  return this.contents !== void 0;
8732
- return identity.isCollection(this.contents) ? this.contents.hasIn(path24) : false;
9040
+ return identity.isCollection(this.contents) ? this.contents.hasIn(path25) : false;
8733
9041
  }
8734
9042
  /**
8735
9043
  * Sets a value in this document. For `!!set`, `value` needs to be a
@@ -8746,13 +9054,13 @@ var require_Document = __commonJS({
8746
9054
  * Sets a value in this document. For `!!set`, `value` needs to be a
8747
9055
  * boolean to add/remove the item from the set.
8748
9056
  */
8749
- setIn(path24, value) {
8750
- if (Collection.isEmptyPath(path24)) {
9057
+ setIn(path25, value) {
9058
+ if (Collection.isEmptyPath(path25)) {
8751
9059
  this.contents = value;
8752
9060
  } else if (this.contents == null) {
8753
- this.contents = Collection.collectionFromPath(this.schema, Array.from(path24), value);
9061
+ this.contents = Collection.collectionFromPath(this.schema, Array.from(path25), value);
8754
9062
  } else if (assertCollection(this.contents)) {
8755
- this.contents.setIn(path24, value);
9063
+ this.contents.setIn(path25, value);
8756
9064
  }
8757
9065
  }
8758
9066
  /**
@@ -10709,9 +11017,9 @@ var require_cst_visit = __commonJS({
10709
11017
  visit.BREAK = BREAK;
10710
11018
  visit.SKIP = SKIP;
10711
11019
  visit.REMOVE = REMOVE;
10712
- visit.itemAtPath = (cst, path24) => {
11020
+ visit.itemAtPath = (cst, path25) => {
10713
11021
  let item = cst;
10714
- for (const [field, index] of path24) {
11022
+ for (const [field, index] of path25) {
10715
11023
  const tok = item?.[field];
10716
11024
  if (tok && "items" in tok) {
10717
11025
  item = tok.items[index];
@@ -10720,23 +11028,23 @@ var require_cst_visit = __commonJS({
10720
11028
  }
10721
11029
  return item;
10722
11030
  };
10723
- visit.parentCollection = (cst, path24) => {
10724
- const parent = visit.itemAtPath(cst, path24.slice(0, -1));
10725
- const field = path24[path24.length - 1][0];
11031
+ visit.parentCollection = (cst, path25) => {
11032
+ const parent = visit.itemAtPath(cst, path25.slice(0, -1));
11033
+ const field = path25[path25.length - 1][0];
10726
11034
  const coll = parent?.[field];
10727
11035
  if (coll && "items" in coll)
10728
11036
  return coll;
10729
11037
  throw new Error("Parent collection not found");
10730
11038
  };
10731
- function _visit(path24, item, visitor) {
10732
- let ctrl = visitor(item, path24);
11039
+ function _visit(path25, item, visitor) {
11040
+ let ctrl = visitor(item, path25);
10733
11041
  if (typeof ctrl === "symbol")
10734
11042
  return ctrl;
10735
11043
  for (const field of ["key", "value"]) {
10736
11044
  const token = item[field];
10737
11045
  if (token && "items" in token) {
10738
11046
  for (let i = 0; i < token.items.length; ++i) {
10739
- const ci = _visit(Object.freeze(path24.concat([[field, i]])), token.items[i], visitor);
11047
+ const ci = _visit(Object.freeze(path25.concat([[field, i]])), token.items[i], visitor);
10740
11048
  if (typeof ci === "number")
10741
11049
  i = ci - 1;
10742
11050
  else if (ci === BREAK)
@@ -10747,10 +11055,10 @@ var require_cst_visit = __commonJS({
10747
11055
  }
10748
11056
  }
10749
11057
  if (typeof ctrl === "function" && field === "key")
10750
- ctrl = ctrl(item, path24);
11058
+ ctrl = ctrl(item, path25);
10751
11059
  }
10752
11060
  }
10753
- return typeof ctrl === "function" ? ctrl(item, path24) : ctrl;
11061
+ return typeof ctrl === "function" ? ctrl(item, path25) : ctrl;
10754
11062
  }
10755
11063
  exports2.visit = visit;
10756
11064
  }
@@ -12035,14 +12343,14 @@ var require_parser = __commonJS({
12035
12343
  case "scalar":
12036
12344
  case "single-quoted-scalar":
12037
12345
  case "double-quoted-scalar": {
12038
- const fs29 = this.flowScalar(this.type);
12346
+ const fs30 = this.flowScalar(this.type);
12039
12347
  if (atNextItem || it.value) {
12040
- map.items.push({ start: start2, key: fs29, sep: [] });
12348
+ map.items.push({ start: start2, key: fs30, sep: [] });
12041
12349
  this.onKeyLine = true;
12042
12350
  } else if (it.sep) {
12043
- this.stack.push(fs29);
12351
+ this.stack.push(fs30);
12044
12352
  } else {
12045
- Object.assign(it, { key: fs29, sep: [] });
12353
+ Object.assign(it, { key: fs30, sep: [] });
12046
12354
  this.onKeyLine = true;
12047
12355
  }
12048
12356
  return;
@@ -12170,13 +12478,13 @@ var require_parser = __commonJS({
12170
12478
  case "scalar":
12171
12479
  case "single-quoted-scalar":
12172
12480
  case "double-quoted-scalar": {
12173
- const fs29 = this.flowScalar(this.type);
12481
+ const fs30 = this.flowScalar(this.type);
12174
12482
  if (!it || it.value)
12175
- fc.items.push({ start: [], key: fs29, sep: [] });
12483
+ fc.items.push({ start: [], key: fs30, sep: [] });
12176
12484
  else if (it.sep)
12177
- this.stack.push(fs29);
12485
+ this.stack.push(fs30);
12178
12486
  else
12179
- Object.assign(it, { key: fs29, sep: [] });
12487
+ Object.assign(it, { key: fs30, sep: [] });
12180
12488
  return;
12181
12489
  }
12182
12490
  case "flow-map-end":
@@ -12943,7 +13251,7 @@ async function handleRecordingCommand(command, args, target, currentSession) {
12943
13251
  throw new Error(`Unknown format: ${format}. Use "browse" (chain JSON), "replay" (Puppeteer), "playwright" (Playwright Test), or "flow" (YAML).`);
12944
13252
  }
12945
13253
  if (filePath) {
12946
- fs15.writeFileSync(filePath, output);
13254
+ fs17.writeFileSync(filePath, output);
12947
13255
  return `Exported ${steps.length} steps as ${format}: ${filePath}`;
12948
13256
  }
12949
13257
  return output;
@@ -12968,7 +13276,7 @@ async function handleRecordingCommand(command, args, target, currentSession) {
12968
13276
  const { formatAsHar: formatAsHar2 } = await Promise.resolve().then(() => (init_har(), har_exports));
12969
13277
  const har = formatAsHar2(sessionBuffers.networkBuffer, recording.startTime);
12970
13278
  const harPath = args[1] || (currentSession ? `${currentSession.outputDir}/recording.har` : `${LOCAL_DIR2}/browse-recording.har`);
12971
- fs15.writeFileSync(harPath, JSON.stringify(har, null, 2));
13279
+ fs17.writeFileSync(harPath, JSON.stringify(har, null, 2));
12972
13280
  const entryCount = har.log.entries.length;
12973
13281
  return `HAR saved: ${harPath} (${entryCount} entries)`;
12974
13282
  }
@@ -13004,11 +13312,11 @@ async function handleRecordingCommand(command, args, target, currentSession) {
13004
13312
  throw new Error(`Unknown recording command: ${command}`);
13005
13313
  }
13006
13314
  }
13007
- var fs15, LOCAL_DIR2;
13315
+ var fs17, LOCAL_DIR2;
13008
13316
  var init_recording = __esm({
13009
13317
  "src/commands/meta/recording.ts"() {
13010
13318
  "use strict";
13011
- fs15 = __toESM(require("fs"), 1);
13319
+ fs17 = __toESM(require("fs"), 1);
13012
13320
  LOCAL_DIR2 = process.env.BROWSE_LOCAL_DIR || "/tmp";
13013
13321
  }
13014
13322
  });
@@ -13032,20 +13340,20 @@ async function saveSessionState(sessionDir, context, encryptionKey) {
13032
13340
  } else {
13033
13341
  content = json;
13034
13342
  }
13035
- fs16.mkdirSync(sessionDir, { recursive: true });
13036
- fs16.writeFileSync(path12.join(sessionDir, STATE_FILENAME), content, { mode: 384 });
13343
+ fs18.mkdirSync(sessionDir, { recursive: true });
13344
+ fs18.writeFileSync(path14.join(sessionDir, STATE_FILENAME), content, { mode: 384 });
13037
13345
  } catch (err) {
13038
13346
  console.log(`[session-persist] Warning: failed to save state: ${err.message}`);
13039
13347
  }
13040
13348
  }
13041
13349
  async function loadSessionState(sessionDir, context, encryptionKey) {
13042
- const statePath = path12.join(sessionDir, STATE_FILENAME);
13043
- if (!fs16.existsSync(statePath)) {
13350
+ const statePath = path14.join(sessionDir, STATE_FILENAME);
13351
+ if (!fs18.existsSync(statePath)) {
13044
13352
  return false;
13045
13353
  }
13046
13354
  let stateData;
13047
13355
  try {
13048
- const raw = fs16.readFileSync(statePath, "utf-8");
13356
+ const raw = fs18.readFileSync(statePath, "utf-8");
13049
13357
  const parsed = JSON.parse(raw);
13050
13358
  if (parsed.encrypted) {
13051
13359
  if (!encryptionKey) {
@@ -13101,24 +13409,24 @@ async function loadSessionState(sessionDir, context, encryptionKey) {
13101
13409
  }
13102
13410
  }
13103
13411
  function hasPersistedState(sessionDir) {
13104
- return fs16.existsSync(path12.join(sessionDir, STATE_FILENAME));
13412
+ return fs18.existsSync(path14.join(sessionDir, STATE_FILENAME));
13105
13413
  }
13106
13414
  function cleanOldStates(localDir, maxAgeDays) {
13107
13415
  const maxAgeMs = maxAgeDays * 24 * 60 * 60 * 1e3;
13108
13416
  const now = Date.now();
13109
13417
  let deleted = 0;
13110
- const statesDir = path12.join(localDir, "states");
13111
- if (fs16.existsSync(statesDir)) {
13418
+ const statesDir = path14.join(localDir, "states");
13419
+ if (fs18.existsSync(statesDir)) {
13112
13420
  try {
13113
- const entries = fs16.readdirSync(statesDir);
13421
+ const entries = fs18.readdirSync(statesDir);
13114
13422
  for (const entry of entries) {
13115
13423
  if (!entry.endsWith(".json")) continue;
13116
- const filePath = path12.join(statesDir, entry);
13424
+ const filePath = path14.join(statesDir, entry);
13117
13425
  try {
13118
- const stat = fs16.statSync(filePath);
13426
+ const stat = fs18.statSync(filePath);
13119
13427
  if (!stat.isFile()) continue;
13120
13428
  if (now - stat.mtimeMs > maxAgeMs) {
13121
- fs16.unlinkSync(filePath);
13429
+ fs18.unlinkSync(filePath);
13122
13430
  deleted++;
13123
13431
  }
13124
13432
  } catch (_) {
@@ -13127,23 +13435,23 @@ function cleanOldStates(localDir, maxAgeDays) {
13127
13435
  } catch (_) {
13128
13436
  }
13129
13437
  }
13130
- const sessionsDir = path12.join(localDir, "sessions");
13131
- if (fs16.existsSync(sessionsDir)) {
13438
+ const sessionsDir = path14.join(localDir, "sessions");
13439
+ if (fs18.existsSync(sessionsDir)) {
13132
13440
  try {
13133
- const sessionDirs = fs16.readdirSync(sessionsDir);
13441
+ const sessionDirs = fs18.readdirSync(sessionsDir);
13134
13442
  for (const dir of sessionDirs) {
13135
- const dirPath = path12.join(sessionsDir, dir);
13443
+ const dirPath = path14.join(sessionsDir, dir);
13136
13444
  try {
13137
- const dirStat = fs16.statSync(dirPath);
13445
+ const dirStat = fs18.statSync(dirPath);
13138
13446
  if (!dirStat.isDirectory()) continue;
13139
13447
  } catch (_) {
13140
13448
  continue;
13141
13449
  }
13142
- const statePath = path12.join(dirPath, STATE_FILENAME);
13450
+ const statePath = path14.join(dirPath, STATE_FILENAME);
13143
13451
  try {
13144
- const stat = fs16.statSync(statePath);
13452
+ const stat = fs18.statSync(statePath);
13145
13453
  if (now - stat.mtimeMs > maxAgeMs) {
13146
- fs16.unlinkSync(statePath);
13454
+ fs18.unlinkSync(statePath);
13147
13455
  deleted++;
13148
13456
  }
13149
13457
  } catch (_) {
@@ -13154,12 +13462,12 @@ function cleanOldStates(localDir, maxAgeDays) {
13154
13462
  }
13155
13463
  return { deleted };
13156
13464
  }
13157
- var fs16, path12, STATE_FILENAME;
13465
+ var fs18, path14, STATE_FILENAME;
13158
13466
  var init_persist = __esm({
13159
13467
  "src/session/persist.ts"() {
13160
13468
  "use strict";
13161
- fs16 = __toESM(require("fs"), 1);
13162
- path12 = __toESM(require("path"), 1);
13469
+ fs18 = __toESM(require("fs"), 1);
13470
+ path14 = __toESM(require("path"), 1);
13163
13471
  init_encryption();
13164
13472
  STATE_FILENAME = "state.json";
13165
13473
  }
@@ -13594,7 +13902,7 @@ async function handleSessionsCommand(command, args, bm, sessionManager2, current
13594
13902
  const lines = entries.map(
13595
13903
  (e) => `[${new Date(e.timestamp).toISOString()}] [${e.level}] ${e.text}`
13596
13904
  ).join("\n") + "\n";
13597
- fs17.appendFileSync(consolePath, lines);
13905
+ fs19.appendFileSync(consolePath, lines);
13598
13906
  buffers.lastConsoleFlushed = buffers.consoleTotalAdded;
13599
13907
  }
13600
13908
  const newNetworkCount = buffers.networkTotalAdded - buffers.lastNetworkFlushed;
@@ -13604,7 +13912,7 @@ async function handleSessionsCommand(command, args, bm, sessionManager2, current
13604
13912
  const lines = entries.map(
13605
13913
  (e) => `[${new Date(e.timestamp).toISOString()}] ${e.method} ${e.url} \u2192 ${e.status || "pending"} (${e.duration || "?"}ms, ${e.size || "?"}B)`
13606
13914
  ).join("\n") + "\n";
13607
- fs17.appendFileSync(networkPath, lines);
13915
+ fs19.appendFileSync(networkPath, lines);
13608
13916
  buffers.lastNetworkFlushed = buffers.networkTotalAdded;
13609
13917
  }
13610
13918
  }
@@ -13620,22 +13928,22 @@ async function handleSessionsCommand(command, args, bm, sessionManager2, current
13620
13928
  const statesDir = `${LOCAL_DIR3}/states`;
13621
13929
  const statePath = `${statesDir}/${name}.json`;
13622
13930
  if (subcommand === "list") {
13623
- if (!fs17.existsSync(statesDir)) return "(no saved states)";
13624
- const files = fs17.readdirSync(statesDir).filter((f) => f.endsWith(".json"));
13931
+ if (!fs19.existsSync(statesDir)) return "(no saved states)";
13932
+ const files = fs19.readdirSync(statesDir).filter((f) => f.endsWith(".json"));
13625
13933
  if (files.length === 0) return "(no saved states)";
13626
13934
  const lines = [];
13627
13935
  for (const file of files) {
13628
13936
  const fp = `${statesDir}/${file}`;
13629
- const stat = fs17.statSync(fp);
13937
+ const stat = fs19.statSync(fp);
13630
13938
  lines.push(` ${file.replace(".json", "")} ${stat.size}B ${new Date(stat.mtimeMs).toISOString()}`);
13631
13939
  }
13632
13940
  return lines.join("\n");
13633
13941
  }
13634
13942
  if (subcommand === "show") {
13635
- if (!fs17.existsSync(statePath)) {
13943
+ if (!fs19.existsSync(statePath)) {
13636
13944
  throw new Error(`State file not found: ${statePath}`);
13637
13945
  }
13638
- const data = JSON.parse(fs17.readFileSync(statePath, "utf-8"));
13946
+ const data = JSON.parse(fs19.readFileSync(statePath, "utf-8"));
13639
13947
  const cookieCount = data.cookies?.length || 0;
13640
13948
  const originCount = data.origins?.length || 0;
13641
13949
  const storageItems = (data.origins || []).reduce((sum, o) => sum + (o.localStorage?.length || 0), 0);
@@ -13660,15 +13968,15 @@ async function handleSessionsCommand(command, args, bm, sessionManager2, current
13660
13968
  const context = bm.getContext();
13661
13969
  if (!context) throw new Error("No browser context");
13662
13970
  const state = await context.storageState();
13663
- fs17.mkdirSync(statesDir, { recursive: true });
13664
- fs17.writeFileSync(statePath, JSON.stringify(state, null, 2), { mode: 384 });
13971
+ fs19.mkdirSync(statesDir, { recursive: true });
13972
+ fs19.writeFileSync(statePath, JSON.stringify(state, null, 2), { mode: 384 });
13665
13973
  return `State saved: ${statePath}`;
13666
13974
  }
13667
13975
  if (subcommand === "load") {
13668
- if (!fs17.existsSync(statePath)) {
13976
+ if (!fs19.existsSync(statePath)) {
13669
13977
  throw new Error(`State file not found: ${statePath}. Run "browse state save ${name}" first.`);
13670
13978
  }
13671
- const stateData = JSON.parse(fs17.readFileSync(statePath, "utf-8"));
13979
+ const stateData = JSON.parse(fs19.readFileSync(statePath, "utf-8"));
13672
13980
  const context = bm.getContext();
13673
13981
  if (!context) throw new Error("No browser context");
13674
13982
  const warnings = [];
@@ -13721,12 +14029,12 @@ ${snapshot}`;
13721
14029
  throw new Error(`Unknown sessions command: ${command}`);
13722
14030
  }
13723
14031
  }
13724
- var fs17, LOCAL_DIR3;
14032
+ var fs19, LOCAL_DIR3;
13725
14033
  var init_sessions = __esm({
13726
14034
  "src/commands/meta/sessions.ts"() {
13727
14035
  "use strict";
13728
14036
  init_sanitize();
13729
- fs17 = __toESM(require("fs"), 1);
14037
+ fs19 = __toESM(require("fs"), 1);
13730
14038
  LOCAL_DIR3 = process.env.BROWSE_LOCAL_DIR || "/tmp";
13731
14039
  }
13732
14040
  });
@@ -14057,11 +14365,11 @@ var init_lib = __esm({
14057
14365
  }
14058
14366
  }
14059
14367
  },
14060
- addToPath: function addToPath(path24, added, removed, oldPosInc, options) {
14061
- var last = path24.lastComponent;
14368
+ addToPath: function addToPath(path25, added, removed, oldPosInc, options) {
14369
+ var last = path25.lastComponent;
14062
14370
  if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) {
14063
14371
  return {
14064
- oldPos: path24.oldPos + oldPosInc,
14372
+ oldPos: path25.oldPos + oldPosInc,
14065
14373
  lastComponent: {
14066
14374
  count: last.count + 1,
14067
14375
  added,
@@ -14071,7 +14379,7 @@ var init_lib = __esm({
14071
14379
  };
14072
14380
  } else {
14073
14381
  return {
14074
- oldPos: path24.oldPos + oldPosInc,
14382
+ oldPos: path25.oldPos + oldPosInc,
14075
14383
  lastComponent: {
14076
14384
  count: 1,
14077
14385
  added,
@@ -14129,7 +14437,7 @@ var init_lib = __esm({
14129
14437
  tokenize: function tokenize(value) {
14130
14438
  return Array.from(value);
14131
14439
  },
14132
- join: function join13(chars) {
14440
+ join: function join15(chars) {
14133
14441
  return chars.join("");
14134
14442
  },
14135
14443
  postProcess: function postProcess(changeObjects) {
@@ -19690,19 +19998,19 @@ __export(detection_exports, {
19690
19998
  detectStack: () => detectStack
19691
19999
  });
19692
20000
  function loadCustomSignatures(localDir) {
19693
- const detectionDir = path13.join(localDir, "detections");
19694
- if (!fs18.existsSync(detectionDir)) return [];
20001
+ const detectionDir = path15.join(localDir, "detections");
20002
+ if (!fs20.existsSync(detectionDir)) return [];
19695
20003
  const sigs = [];
19696
20004
  let entries;
19697
20005
  try {
19698
- entries = fs18.readdirSync(detectionDir).filter((f) => f.endsWith(".json"));
20006
+ entries = fs20.readdirSync(detectionDir).filter((f) => f.endsWith(".json"));
19699
20007
  } catch {
19700
20008
  return [];
19701
20009
  }
19702
20010
  for (const entry of entries) {
19703
- const filePath = path13.join(detectionDir, entry);
20011
+ const filePath = path15.join(detectionDir, entry);
19704
20012
  try {
19705
- const raw = fs18.readFileSync(filePath, "utf-8");
20013
+ const raw = fs20.readFileSync(filePath, "utf-8");
19706
20014
  const parsed = JSON.parse(raw);
19707
20015
  if (parsed == null || typeof parsed !== "object" || parsed.version !== 1 || typeof parsed.name !== "string" || typeof parsed.detect !== "string") {
19708
20016
  process.stderr.write(
@@ -19828,15 +20136,15 @@ async function detectStack(page, networkEntries, localDir) {
19828
20136
  const thirdParty = buildThirdPartyInventory(page, networkEntries ?? []);
19829
20137
  return { frameworks, saas, infrastructure, thirdParty, custom };
19830
20138
  }
19831
- var fs18, path13, THIRD_PARTY_DOMAINS;
20139
+ var fs20, path15, THIRD_PARTY_DOMAINS;
19832
20140
  var init_detection = __esm({
19833
20141
  "src/detection/index.ts"() {
19834
20142
  "use strict";
19835
20143
  init_frameworks();
19836
20144
  init_saas();
19837
20145
  init_infrastructure();
19838
- fs18 = __toESM(require("fs"), 1);
19839
- path13 = __toESM(require("path"), 1);
20146
+ fs20 = __toESM(require("fs"), 1);
20147
+ path15 = __toESM(require("path"), 1);
19840
20148
  THIRD_PARTY_DOMAINS = {
19841
20149
  // Analytics
19842
20150
  "google-analytics.com": "analytics",
@@ -21918,35 +22226,35 @@ __export(persist_exports2, {
21918
22226
  saveAudit: () => saveAudit
21919
22227
  });
21920
22228
  function auditsDir(localDir) {
21921
- return path14.join(localDir, "audits");
22229
+ return path16.join(localDir, "audits");
21922
22230
  }
21923
22231
  function auditPath(localDir, name) {
21924
- return path14.join(auditsDir(localDir), `${sanitizeName(name)}.json`);
22232
+ return path16.join(auditsDir(localDir), `${sanitizeName(name)}.json`);
21925
22233
  }
21926
22234
  function saveAudit(localDir, name, report) {
21927
22235
  const dir = auditsDir(localDir);
21928
- fs19.mkdirSync(dir, { recursive: true });
22236
+ fs21.mkdirSync(dir, { recursive: true });
21929
22237
  const filePath = auditPath(localDir, name);
21930
- fs19.writeFileSync(filePath, JSON.stringify(report, null, 2), { mode: 384 });
22238
+ fs21.writeFileSync(filePath, JSON.stringify(report, null, 2), { mode: 384 });
21931
22239
  return filePath;
21932
22240
  }
21933
22241
  function loadAudit(localDir, name) {
21934
22242
  const filePath = auditPath(localDir, name);
21935
- if (!fs19.existsSync(filePath)) {
22243
+ if (!fs21.existsSync(filePath)) {
21936
22244
  throw new Error(
21937
22245
  `Audit not found: ${filePath}. Run "browse perf-audit save ${name}" first.`
21938
22246
  );
21939
22247
  }
21940
- return JSON.parse(fs19.readFileSync(filePath, "utf-8"));
22248
+ return JSON.parse(fs21.readFileSync(filePath, "utf-8"));
21941
22249
  }
21942
22250
  function listAudits(localDir) {
21943
22251
  const dir = auditsDir(localDir);
21944
- if (!fs19.existsSync(dir)) return [];
21945
- const files = fs19.readdirSync(dir).filter((f) => f.endsWith(".json"));
22252
+ if (!fs21.existsSync(dir)) return [];
22253
+ const files = fs21.readdirSync(dir).filter((f) => f.endsWith(".json"));
21946
22254
  if (files.length === 0) return [];
21947
22255
  const entries = files.map((file) => {
21948
- const fp = path14.join(dir, file);
21949
- const stat = fs19.statSync(fp);
22256
+ const fp = path16.join(dir, file);
22257
+ const stat = fs21.statSync(fp);
21950
22258
  return {
21951
22259
  name: file.replace(".json", ""),
21952
22260
  sizeBytes: stat.size,
@@ -21958,19 +22266,19 @@ function listAudits(localDir) {
21958
22266
  }
21959
22267
  function deleteAudit(localDir, name) {
21960
22268
  const filePath = auditPath(localDir, name);
21961
- if (!fs19.existsSync(filePath)) {
22269
+ if (!fs21.existsSync(filePath)) {
21962
22270
  throw new Error(
21963
22271
  `Audit not found: ${filePath}. Nothing to delete.`
21964
22272
  );
21965
22273
  }
21966
- fs19.unlinkSync(filePath);
22274
+ fs21.unlinkSync(filePath);
21967
22275
  }
21968
- var fs19, path14;
22276
+ var fs21, path16;
21969
22277
  var init_persist2 = __esm({
21970
22278
  "src/perf-audit/persist.ts"() {
21971
22279
  "use strict";
21972
- fs19 = __toESM(require("fs"), 1);
21973
- path14 = __toESM(require("path"), 1);
22280
+ fs21 = __toESM(require("fs"), 1);
22281
+ path16 = __toESM(require("path"), 1);
21974
22282
  init_sanitize();
21975
22283
  }
21976
22284
  });
@@ -23328,7 +23636,7 @@ All budgets met.`;
23328
23636
  }
23329
23637
  return "OK";
23330
23638
  }
23331
- await new Promise((resolve9) => setTimeout(resolve9, 100));
23639
+ await new Promise((resolve10) => setTimeout(resolve10, 100));
23332
23640
  }
23333
23641
  const failures = lastResults.filter((r2) => !r2.passed);
23334
23642
  throw new Error(
@@ -23417,12 +23725,12 @@ async function autoDetectSelector(page, field) {
23417
23725
  }
23418
23726
  throw new Error("Could not auto-detect submit button.");
23419
23727
  }
23420
- var fs20, path15, AuthVault;
23728
+ var fs22, path17, AuthVault;
23421
23729
  var init_auth_vault = __esm({
23422
23730
  "src/security/auth-vault.ts"() {
23423
23731
  "use strict";
23424
- fs20 = __toESM(require("fs"), 1);
23425
- path15 = __toESM(require("path"), 1);
23732
+ fs22 = __toESM(require("fs"), 1);
23733
+ path17 = __toESM(require("path"), 1);
23426
23734
  init_constants();
23427
23735
  init_encryption();
23428
23736
  init_sanitize();
@@ -23430,11 +23738,11 @@ var init_auth_vault = __esm({
23430
23738
  authDir;
23431
23739
  encryptionKey;
23432
23740
  constructor(localDir) {
23433
- this.authDir = path15.join(localDir, "auth");
23741
+ this.authDir = path17.join(localDir, "auth");
23434
23742
  this.encryptionKey = resolveEncryptionKey(localDir);
23435
23743
  }
23436
23744
  save(name, url, username, password, selectors) {
23437
- fs20.mkdirSync(this.authDir, { recursive: true });
23745
+ fs22.mkdirSync(this.authDir, { recursive: true });
23438
23746
  const { ciphertext, iv, authTag } = encrypt(password, this.encryptionKey);
23439
23747
  const now = (/* @__PURE__ */ new Date()).toISOString();
23440
23748
  const credential = {
@@ -23451,15 +23759,15 @@ var init_auth_vault = __esm({
23451
23759
  createdAt: now,
23452
23760
  updatedAt: now
23453
23761
  };
23454
- const filePath = path15.join(this.authDir, `${sanitizeName(name)}.json`);
23455
- fs20.writeFileSync(filePath, JSON.stringify(credential, null, 2), { mode: 384 });
23762
+ const filePath = path17.join(this.authDir, `${sanitizeName(name)}.json`);
23763
+ fs22.writeFileSync(filePath, JSON.stringify(credential, null, 2), { mode: 384 });
23456
23764
  }
23457
23765
  load(name) {
23458
- const filePath = path15.join(this.authDir, `${sanitizeName(name)}.json`);
23459
- if (!fs20.existsSync(filePath)) {
23766
+ const filePath = path17.join(this.authDir, `${sanitizeName(name)}.json`);
23767
+ if (!fs22.existsSync(filePath)) {
23460
23768
  throw new Error(`Credential "${name}" not found. Run "browse auth list" to see saved credentials.`);
23461
23769
  }
23462
- return JSON.parse(fs20.readFileSync(filePath, "utf-8"));
23770
+ return JSON.parse(fs22.readFileSync(filePath, "utf-8"));
23463
23771
  }
23464
23772
  async login(name, bm) {
23465
23773
  const cred = this.load(name);
@@ -23480,11 +23788,11 @@ var init_auth_vault = __esm({
23480
23788
  return `Logged in as ${cred.username} at ${page.url()}`;
23481
23789
  }
23482
23790
  list() {
23483
- if (!fs20.existsSync(this.authDir)) return [];
23484
- const files = fs20.readdirSync(this.authDir).filter((f) => f.endsWith(".json"));
23791
+ if (!fs22.existsSync(this.authDir)) return [];
23792
+ const files = fs22.readdirSync(this.authDir).filter((f) => f.endsWith(".json"));
23485
23793
  return files.map((f) => {
23486
23794
  try {
23487
- const data = JSON.parse(fs20.readFileSync(path15.join(this.authDir, f), "utf-8"));
23795
+ const data = JSON.parse(fs22.readFileSync(path17.join(this.authDir, f), "utf-8"));
23488
23796
  return {
23489
23797
  name: data.name,
23490
23798
  url: data.url,
@@ -23498,11 +23806,11 @@ var init_auth_vault = __esm({
23498
23806
  }).filter(Boolean);
23499
23807
  }
23500
23808
  delete(name) {
23501
- const filePath = path15.join(this.authDir, `${sanitizeName(name)}.json`);
23502
- if (!fs20.existsSync(filePath)) {
23809
+ const filePath = path17.join(this.authDir, `${sanitizeName(name)}.json`);
23810
+ if (!fs22.existsSync(filePath)) {
23503
23811
  throw new Error(`Credential "${name}" not found.`);
23504
23812
  }
23505
- fs20.unlinkSync(filePath);
23813
+ fs22.unlinkSync(filePath);
23506
23814
  }
23507
23815
  };
23508
23816
  }
@@ -23639,20 +23947,20 @@ __export(policy_exports, {
23639
23947
  function findFileUpward(filename) {
23640
23948
  let dir = process.cwd();
23641
23949
  for (let i = 0; i < 20; i++) {
23642
- const candidate = path16.join(dir, filename);
23643
- if (fs21.existsSync(candidate)) return candidate;
23644
- const parent = path16.dirname(dir);
23950
+ const candidate = path18.join(dir, filename);
23951
+ if (fs23.existsSync(candidate)) return candidate;
23952
+ const parent = path18.dirname(dir);
23645
23953
  if (parent === dir) break;
23646
23954
  dir = parent;
23647
23955
  }
23648
23956
  return null;
23649
23957
  }
23650
- var fs21, path16, PolicyChecker;
23958
+ var fs23, path18, PolicyChecker;
23651
23959
  var init_policy = __esm({
23652
23960
  "src/security/policy.ts"() {
23653
23961
  "use strict";
23654
- fs21 = __toESM(require("fs"), 1);
23655
- path16 = __toESM(require("path"), 1);
23962
+ fs23 = __toESM(require("fs"), 1);
23963
+ path18 = __toESM(require("path"), 1);
23656
23964
  PolicyChecker = class {
23657
23965
  filePath = null;
23658
23966
  lastMtime = 0;
@@ -23671,10 +23979,10 @@ var init_policy = __esm({
23671
23979
  reload() {
23672
23980
  if (!this.filePath) return;
23673
23981
  try {
23674
- const stat = fs21.statSync(this.filePath);
23982
+ const stat = fs23.statSync(this.filePath);
23675
23983
  if (stat.mtimeMs === this.lastMtime) return;
23676
23984
  this.lastMtime = stat.mtimeMs;
23677
- const raw = fs21.readFileSync(this.filePath, "utf-8");
23985
+ const raw = fs23.readFileSync(this.filePath, "utf-8");
23678
23986
  this.policy = JSON.parse(raw);
23679
23987
  } catch {
23680
23988
  }
@@ -23851,110 +24159,6 @@ var init_buffers = __esm({
23851
24159
  }
23852
24160
  });
23853
24161
 
23854
- // src/app/macos/bridge.ts
23855
- var bridge_exports3 = {};
23856
- __export(bridge_exports3, {
23857
- createMacOSBridge: () => createMacOSBridge,
23858
- ensureMacOSBridge: () => ensureMacOSBridge,
23859
- resolveBridgePath: () => resolveBridgePath
23860
- });
23861
- function resolveBridgePath() {
23862
- const candidates = [
23863
- // 1. Local dev build
23864
- path17.resolve(__dirname_bridge, "../../../browse-ax/.build/release/browse-ax"),
23865
- path17.resolve(__dirname_bridge, "../../../browse-ax/.build/debug/browse-ax"),
23866
- // 2. Installed alongside source (bin/ at project root)
23867
- path17.resolve(__dirname_bridge, "../../bin/browse-ax"),
23868
- // 3. Bundled build (dist/browse.cjs → ../bin/)
23869
- path17.resolve(__dirname_bridge, "../bin/browse-ax")
23870
- ];
23871
- for (const p of candidates) {
23872
- if (fs22.existsSync(p)) return p;
23873
- }
23874
- const lazyPath = path17.join(
23875
- process.env.BROWSE_LOCAL_DIR || path17.join(process.cwd(), ".browse"),
23876
- "bin",
23877
- "browse-ax"
23878
- );
23879
- if (fs22.existsSync(lazyPath)) return lazyPath;
23880
- throw new Error(
23881
- "browse-ax binary not found. Run: browse enable macos\nOr build manually: cd browse-ax && swift build -c release"
23882
- );
23883
- }
23884
- async function ensureMacOSBridge() {
23885
- if (process.platform !== "darwin") {
23886
- throw new Error("App automation requires macOS (uses Accessibility API)");
23887
- }
23888
- return resolveBridgePath();
23889
- }
23890
- async function execBridge(bridgePath, args) {
23891
- return new Promise((resolve9, reject) => {
23892
- const proc = (0, import_child_process9.spawn)(bridgePath, args, {
23893
- stdio: ["ignore", "pipe", "pipe"]
23894
- });
23895
- const chunks = [];
23896
- const errChunks = [];
23897
- proc.stdout.on("data", (c) => chunks.push(c));
23898
- proc.stderr.on("data", (c) => errChunks.push(c));
23899
- proc.on("close", (code) => {
23900
- const stdout = Buffer.concat(chunks).toString("utf-8").trim();
23901
- const stderr = Buffer.concat(errChunks).toString("utf-8").trim();
23902
- if (code !== 0) {
23903
- try {
23904
- const err = JSON.parse(stderr || stdout);
23905
- reject(new Error(err.error || `Bridge exited with code ${code}`));
23906
- } catch {
23907
- reject(new Error(stderr || `Bridge exited with code ${code}`));
23908
- }
23909
- return;
23910
- }
23911
- try {
23912
- resolve9(JSON.parse(stdout));
23913
- } catch {
23914
- reject(new Error(`Invalid bridge output: ${stdout.slice(0, 200)}`));
23915
- }
23916
- });
23917
- });
23918
- }
23919
- function createMacOSBridge(bridgePath, pid) {
23920
- const base = ["--pid", String(pid)];
23921
- return {
23922
- async tree() {
23923
- return execBridge(bridgePath, [...base, "tree"]);
23924
- },
23925
- async action(nodePath, actionName) {
23926
- return execBridge(bridgePath, [...base, "action", JSON.stringify(nodePath), actionName]);
23927
- },
23928
- async setValue(nodePath, value) {
23929
- return execBridge(bridgePath, [...base, "set-value", JSON.stringify(nodePath), value]);
23930
- },
23931
- async type(text) {
23932
- return execBridge(bridgePath, [...base, "type", text]);
23933
- },
23934
- async press(key) {
23935
- return execBridge(bridgePath, [...base, "press", key]);
23936
- },
23937
- async screenshot(outputPath) {
23938
- return execBridge(bridgePath, [...base, "screenshot", outputPath]);
23939
- },
23940
- async state() {
23941
- return execBridge(bridgePath, [...base, "state"]);
23942
- }
23943
- };
23944
- }
23945
- var import_child_process9, path17, fs22, import_url6, __filename_bridge2, __dirname_bridge;
23946
- var init_bridge3 = __esm({
23947
- "src/app/macos/bridge.ts"() {
23948
- "use strict";
23949
- import_child_process9 = require("child_process");
23950
- path17 = __toESM(require("path"), 1);
23951
- fs22 = __toESM(require("fs"), 1);
23952
- import_url6 = require("url");
23953
- __filename_bridge2 = (0, import_url6.fileURLToPath)(__import_meta_url);
23954
- __dirname_bridge = path17.dirname(__filename_bridge2);
23955
- }
23956
- });
23957
-
23958
24162
  // src/commands/meta/system.ts
23959
24163
  async function handleSystemCommand(command, args, target, shutdown2, sessionManager2, currentSession, lifecycle) {
23960
24164
  switch (command) {
@@ -24056,7 +24260,7 @@ async function handleSystemCommand(command, args, target, shutdown2, sessionMana
24056
24260
  return results.join("\n\n");
24057
24261
  }
24058
24262
  case "doctor": {
24059
- const { execSync: execSync7 } = await import("child_process");
24263
+ const { execSync: execSync8 } = await import("child_process");
24060
24264
  const platformArg = args.find((a) => a.startsWith("--platform="))?.split("=")[1] || (args.indexOf("--platform") !== -1 ? args[args.indexOf("--platform") + 1] : "");
24061
24265
  const lines = [];
24062
24266
  lines.push(`Node: ${process.version}`);
@@ -24086,7 +24290,7 @@ async function handleSystemCommand(command, args, target, shutdown2, sessionMana
24086
24290
  lines.push("");
24087
24291
  lines.push("--- Android ---");
24088
24292
  try {
24089
- const adbVersion = execSync7("adb version", { encoding: "utf-8", timeout: 5e3 }).split("\n")[0].trim();
24293
+ const adbVersion = execSync8("adb version", { encoding: "utf-8", timeout: 5e3 }).split("\n")[0].trim();
24090
24294
  lines.push(`adb: ${adbVersion}`);
24091
24295
  } catch {
24092
24296
  lines.push("adb: NOT FOUND \u2014 install Android SDK platform-tools and add to PATH");
@@ -24094,7 +24298,7 @@ async function handleSystemCommand(command, args, target, shutdown2, sessionMana
24094
24298
  if (platformArg === "android") return lines.join("\n");
24095
24299
  }
24096
24300
  try {
24097
- const devicesOut = execSync7("adb devices", { encoding: "utf-8", timeout: 5e3 });
24301
+ const devicesOut = execSync8("adb devices", { encoding: "utf-8", timeout: 5e3 });
24098
24302
  const deviceLines = devicesOut.split("\n").slice(1).map((l) => l.trim()).filter((l) => l.length > 0);
24099
24303
  const booted = deviceLines.filter((l) => l.endsWith(" device"));
24100
24304
  const other = deviceLines.filter((l) => !l.endsWith(" device"));
@@ -24111,8 +24315,8 @@ async function handleSystemCommand(command, args, target, shutdown2, sessionMana
24111
24315
  lines.push("Devices: could not query (adb failed)");
24112
24316
  }
24113
24317
  try {
24114
- execSync7("emulator -list-avds", { encoding: "utf-8", timeout: 5e3, stdio: ["ignore", "pipe", "pipe"] });
24115
- const avds = execSync7("emulator -list-avds", { encoding: "utf-8", timeout: 5e3 }).trim();
24318
+ execSync8("emulator -list-avds", { encoding: "utf-8", timeout: 5e3, stdio: ["ignore", "pipe", "pipe"] });
24319
+ const avds = execSync8("emulator -list-avds", { encoding: "utf-8", timeout: 5e3 }).trim();
24116
24320
  if (avds) {
24117
24321
  lines.push(`AVDs: ${avds.split("\n").join(", ")}`);
24118
24322
  } else {
@@ -24122,13 +24326,13 @@ async function handleSystemCommand(command, args, target, shutdown2, sessionMana
24122
24326
  lines.push("Emulator: not found (optional \u2014 only needed to start AVDs from CLI)");
24123
24327
  }
24124
24328
  try {
24125
- const fs29 = await import("fs");
24126
- const path24 = await import("path");
24127
- const localBuild = path24.resolve(__dirname, "../../../browse-android/app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk");
24128
- const installed = path24.resolve(__dirname, "../../bin/browse-android.apk");
24129
- if (fs29.existsSync(localBuild)) {
24329
+ const fs30 = await import("fs");
24330
+ const path25 = await import("path");
24331
+ const localBuild = path25.resolve(__dirname, "../../../browse-android/app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk");
24332
+ const installed = path25.resolve(__dirname, "../../bin/browse-android.apk");
24333
+ if (fs30.existsSync(localBuild)) {
24130
24334
  lines.push(`Driver APK: ${localBuild}`);
24131
- } else if (fs29.existsSync(installed)) {
24335
+ } else if (fs30.existsSync(installed)) {
24132
24336
  lines.push(`Driver APK: ${installed}`);
24133
24337
  } else {
24134
24338
  lines.push("Driver APK: NOT FOUND");
@@ -24141,9 +24345,9 @@ async function handleSystemCommand(command, args, target, shutdown2, sessionMana
24141
24345
  return lines.join("\n");
24142
24346
  }
24143
24347
  case "upgrade": {
24144
- const { execSync: execSync7 } = await import("child_process");
24348
+ const { execSync: execSync8 } = await import("child_process");
24145
24349
  try {
24146
- const output = execSync7("npm update -g @ulpi/browse 2>&1", { encoding: "utf-8", timeout: 3e4 });
24350
+ const output = execSync8("npm update -g @ulpi/browse 2>&1", { encoding: "utf-8", timeout: 3e4 });
24147
24351
  return `Upgrade complete.
24148
24352
  ${output.trim()}`;
24149
24353
  } catch (err) {
@@ -24450,21 +24654,21 @@ var init_refs = __esm({
24450
24654
  function getProfileDir(localDir, name) {
24451
24655
  const sanitized = sanitizeName(name);
24452
24656
  if (!sanitized) throw new Error("Invalid profile name");
24453
- return path18.join(localDir, "profiles", sanitized);
24657
+ return path19.join(localDir, "profiles", sanitized);
24454
24658
  }
24455
24659
  function listProfiles(localDir) {
24456
- const profilesDir = path18.join(localDir, "profiles");
24457
- if (!fs23.existsSync(profilesDir)) return [];
24458
- return fs23.readdirSync(profilesDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
24459
- const dir = path18.join(profilesDir, d.name);
24460
- const stat = fs23.statSync(dir);
24660
+ const profilesDir = path19.join(localDir, "profiles");
24661
+ if (!fs24.existsSync(profilesDir)) return [];
24662
+ return fs24.readdirSync(profilesDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
24663
+ const dir = path19.join(profilesDir, d.name);
24664
+ const stat = fs24.statSync(dir);
24461
24665
  let totalSize = 0;
24462
24666
  try {
24463
- const files = fs23.readdirSync(dir, { recursive: true, withFileTypes: true });
24667
+ const files = fs24.readdirSync(dir, { recursive: true, withFileTypes: true });
24464
24668
  for (const f of files) {
24465
24669
  if (f.isFile()) {
24466
24670
  try {
24467
- totalSize += fs23.statSync(path18.join(f.parentPath || f.path || dir, f.name)).size;
24671
+ totalSize += fs24.statSync(path19.join(f.parentPath || f.path || dir, f.name)).size;
24468
24672
  } catch {
24469
24673
  }
24470
24674
  }
@@ -24481,15 +24685,15 @@ function listProfiles(localDir) {
24481
24685
  }
24482
24686
  function deleteProfile(localDir, name) {
24483
24687
  const dir = getProfileDir(localDir, name);
24484
- if (!fs23.existsSync(dir)) throw new Error(`Profile "${name}" not found`);
24485
- fs23.rmSync(dir, { recursive: true, force: true });
24688
+ if (!fs24.existsSync(dir)) throw new Error(`Profile "${name}" not found`);
24689
+ fs24.rmSync(dir, { recursive: true, force: true });
24486
24690
  }
24487
- var path18, fs23;
24691
+ var path19, fs24;
24488
24692
  var init_profiles = __esm({
24489
24693
  "src/browser/profiles.ts"() {
24490
24694
  "use strict";
24491
- path18 = __toESM(require("path"), 1);
24492
- fs23 = __toESM(require("fs"), 1);
24695
+ path19 = __toESM(require("path"), 1);
24696
+ fs24 = __toESM(require("fs"), 1);
24493
24697
  init_sanitize();
24494
24698
  }
24495
24699
  });
@@ -24663,9 +24867,9 @@ var init_manager = __esm({
24663
24867
  });
24664
24868
  } catch (err) {
24665
24869
  if (err.message?.includes("Failed to launch") || err.message?.includes("Target closed")) {
24666
- const fs29 = await import("fs");
24870
+ const fs30 = await import("fs");
24667
24871
  console.error(`[browse] Profile directory corrupted, recreating: ${profileDir}`);
24668
- fs29.rmSync(profileDir, { recursive: true, force: true });
24872
+ fs30.rmSync(profileDir, { recursive: true, force: true });
24669
24873
  context = await import_playwright2.chromium.launchPersistentContext(profileDir, {
24670
24874
  headless: process.env.BROWSE_HEADED !== "1",
24671
24875
  viewport: { width: 1920, height: 1080 }
@@ -25393,8 +25597,8 @@ var init_manager = __esm({
25393
25597
  // ─── Video Recording ──────────────────────────────────────
25394
25598
  async startVideoRecording(dir) {
25395
25599
  if (this.videoRecording) throw new Error("Video recording already active");
25396
- const fs29 = await import("fs");
25397
- fs29.mkdirSync(dir, { recursive: true });
25600
+ const fs30 = await import("fs");
25601
+ fs30.mkdirSync(dir, { recursive: true });
25398
25602
  this.videoRecording = { dir, startedAt: Date.now() };
25399
25603
  const viewport = this.currentDevice?.viewport || { width: 1920, height: 1080 };
25400
25604
  await this.recreateContext({
@@ -25691,10 +25895,10 @@ __export(react_devtools_exports, {
25691
25895
  requireReact: () => requireReact
25692
25896
  });
25693
25897
  async function ensureHook() {
25694
- if (fs24.existsSync(HOOK_PATH)) {
25695
- return fs24.readFileSync(HOOK_PATH, "utf8");
25898
+ if (fs25.existsSync(HOOK_PATH)) {
25899
+ return fs25.readFileSync(HOOK_PATH, "utf8");
25696
25900
  }
25697
- fs24.mkdirSync(CACHE_DIR, { recursive: true });
25901
+ fs25.mkdirSync(CACHE_DIR, { recursive: true });
25698
25902
  const res = await fetch(HOOK_URL);
25699
25903
  if (!res.ok) {
25700
25904
  throw new Error(
@@ -25703,7 +25907,7 @@ Manual fallback: npm install -g react-devtools-core, then copy installHook.js to
25703
25907
  );
25704
25908
  }
25705
25909
  const script = await res.text();
25706
- fs24.writeFileSync(HOOK_PATH, script);
25910
+ fs25.writeFileSync(HOOK_PATH, script);
25707
25911
  return script;
25708
25912
  }
25709
25913
  async function injectHook(bm) {
@@ -25850,15 +26054,15 @@ async function getSuspense(bm, page) {
25850
26054
  if (!roots || roots.size === 0) return "No fiber roots found";
25851
26055
  const root = roots.values().next().value;
25852
26056
  const boundaries = [];
25853
- const walk = (fiber, path24) => {
26057
+ const walk = (fiber, path25) => {
25854
26058
  if (!fiber) return;
25855
26059
  if (fiber.tag === 13) {
25856
26060
  const resolved = fiber.memoizedState === null;
25857
- const parent = path24.length > 0 ? path24[path24.length - 1] : "root";
26061
+ const parent = path25.length > 0 ? path25[path25.length - 1] : "root";
25858
26062
  boundaries.push(`Suspense in ${parent} \u2014 ${resolved ? "resolved (children visible)" : "pending (showing fallback)"}`);
25859
26063
  }
25860
26064
  const name = fiber.type?.displayName || fiber.type?.name || null;
25861
- const newPath = name ? [...path24, name] : path24;
26065
+ const newPath = name ? [...path25, name] : path25;
25862
26066
  let child = fiber.child;
25863
26067
  while (child) {
25864
26068
  walk(child, newPath);
@@ -26037,15 +26241,15 @@ async function getContext(bm, page, selector) {
26037
26241
  await handle.dispose();
26038
26242
  return result;
26039
26243
  }
26040
- var fs24, path19, os3, CACHE_DIR, HOOK_PATH, HOOK_URL;
26244
+ var fs25, path20, os4, CACHE_DIR, HOOK_PATH, HOOK_URL;
26041
26245
  var init_react_devtools = __esm({
26042
26246
  "src/browser/react-devtools.ts"() {
26043
26247
  "use strict";
26044
- fs24 = __toESM(require("fs"), 1);
26045
- path19 = __toESM(require("path"), 1);
26046
- os3 = __toESM(require("os"), 1);
26047
- CACHE_DIR = path19.join(os3.homedir(), ".cache", "browse", "react-devtools");
26048
- HOOK_PATH = path19.join(CACHE_DIR, "installHook.js");
26248
+ fs25 = __toESM(require("fs"), 1);
26249
+ path20 = __toESM(require("path"), 1);
26250
+ os4 = __toESM(require("os"), 1);
26251
+ CACHE_DIR = path20.join(os4.homedir(), ".cache", "browse", "react-devtools");
26252
+ HOOK_PATH = path20.join(CACHE_DIR, "installHook.js");
26049
26253
  HOOK_URL = "https://unpkg.com/react-devtools-core@latest/dist/installHook.js";
26050
26254
  }
26051
26255
  });
@@ -26333,9 +26537,9 @@ function getFlowsDir() {
26333
26537
  const root = findProjectRoot();
26334
26538
  if (config.flowPaths?.length && root) {
26335
26539
  for (const fp of config.flowPaths) {
26336
- const abs = path20.isAbsolute(fp) ? fp : path20.join(root, fp);
26540
+ const abs = path21.isAbsolute(fp) ? fp : path21.join(root, fp);
26337
26541
  try {
26338
- fs25.mkdirSync(abs, { recursive: true });
26542
+ fs26.mkdirSync(abs, { recursive: true });
26339
26543
  return abs;
26340
26544
  } catch {
26341
26545
  }
@@ -26343,17 +26547,17 @@ function getFlowsDir() {
26343
26547
  }
26344
26548
  const localDir = process.env.BROWSE_LOCAL_DIR;
26345
26549
  if (localDir) {
26346
- const dir2 = path20.join(localDir, "flows");
26347
- fs25.mkdirSync(dir2, { recursive: true });
26550
+ const dir2 = path21.join(localDir, "flows");
26551
+ fs26.mkdirSync(dir2, { recursive: true });
26348
26552
  return dir2;
26349
26553
  }
26350
26554
  if (root) {
26351
- const dir2 = path20.join(root, ".browse", "flows");
26352
- fs25.mkdirSync(dir2, { recursive: true });
26555
+ const dir2 = path21.join(root, ".browse", "flows");
26556
+ fs26.mkdirSync(dir2, { recursive: true });
26353
26557
  return dir2;
26354
26558
  }
26355
- const dir = path20.join(process.cwd(), ".browse", "flows");
26356
- fs25.mkdirSync(dir, { recursive: true });
26559
+ const dir = path21.join(process.cwd(), ".browse", "flows");
26560
+ fs26.mkdirSync(dir, { recursive: true });
26357
26561
  return dir;
26358
26562
  }
26359
26563
  function validateFlowName(name) {
@@ -26473,18 +26677,18 @@ async function handleFlowsCommand(command, args, target, shutdown2, sessionManag
26473
26677
  }
26474
26678
  const { exportFlowYaml: exportFlowYaml2 } = await Promise.resolve().then(() => (init_record(), record_exports));
26475
26679
  const yamlContent = exportFlowYaml2(steps2);
26476
- const flowPath = path20.join(getFlowsDir(), `${name}.yaml`);
26477
- fs25.writeFileSync(flowPath, yamlContent, "utf-8");
26680
+ const flowPath = path21.join(getFlowsDir(), `${name}.yaml`);
26681
+ fs26.writeFileSync(flowPath, yamlContent, "utf-8");
26478
26682
  return `Flow saved: ${flowPath} (${steps2.length} steps)`;
26479
26683
  }
26480
26684
  if (subOrFile === "run") {
26481
26685
  const name = args[1];
26482
26686
  if (!name) throw new Error("Usage: browse flow run <name>");
26483
26687
  validateFlowName(name);
26484
- const flowPath = path20.join(getFlowsDir(), `${name}.yaml`);
26688
+ const flowPath = path21.join(getFlowsDir(), `${name}.yaml`);
26485
26689
  let content2;
26486
26690
  try {
26487
- content2 = fs25.readFileSync(flowPath, "utf-8");
26691
+ content2 = fs26.readFileSync(flowPath, "utf-8");
26488
26692
  } catch (err) {
26489
26693
  if (err.code === "ENOENT") {
26490
26694
  throw new Error(`Saved flow not found: "${name}" (looked at ${flowPath})`);
@@ -26497,18 +26701,18 @@ async function handleFlowsCommand(command, args, target, shutdown2, sessionManag
26497
26701
  }
26498
26702
  if (subOrFile === "list") {
26499
26703
  const flowsDir = getFlowsDir();
26500
- if (!fs25.existsSync(flowsDir)) {
26704
+ if (!fs26.existsSync(flowsDir)) {
26501
26705
  return "No saved flows (directory does not exist yet)";
26502
26706
  }
26503
- const entries = fs25.readdirSync(flowsDir).filter((f) => f.endsWith(".yaml") || f.endsWith(".yml")).sort();
26707
+ const entries = fs26.readdirSync(flowsDir).filter((f) => f.endsWith(".yaml") || f.endsWith(".yml")).sort();
26504
26708
  if (entries.length === 0) {
26505
26709
  return "No saved flows";
26506
26710
  }
26507
26711
  const lines = [`Saved flows (${flowsDir}):`];
26508
26712
  for (const entry of entries) {
26509
26713
  const name = entry.replace(/\.(yaml|yml)$/, "");
26510
- const fullPath = path20.join(flowsDir, entry);
26511
- const stat = fs25.statSync(fullPath);
26714
+ const fullPath = path21.join(flowsDir, entry);
26715
+ const stat = fs26.statSync(fullPath);
26512
26716
  const mtime = new Date(stat.mtimeMs).toISOString().replace("T", " ").slice(0, 19);
26513
26717
  lines.push(` ${name} (${mtime})`);
26514
26718
  }
@@ -26517,7 +26721,7 @@ async function handleFlowsCommand(command, args, target, shutdown2, sessionManag
26517
26721
  const filePath = subOrFile;
26518
26722
  let content;
26519
26723
  try {
26520
- content = fs25.readFileSync(filePath, "utf-8");
26724
+ content = fs26.readFileSync(filePath, "utf-8");
26521
26725
  } catch (err) {
26522
26726
  if (err.code === "ENOENT") {
26523
26727
  throw new Error(`Flow file not found: ${filePath}`);
@@ -26559,7 +26763,7 @@ async function handleFlowsCommand(command, args, target, shutdown2, sessionManag
26559
26763
  lastError = err.message;
26560
26764
  if (attempt < maxAttempts) {
26561
26765
  if (backoff) {
26562
- await new Promise((resolve9) => setTimeout(resolve9, delay));
26766
+ await new Promise((resolve10) => setTimeout(resolve10, delay));
26563
26767
  delay *= 2;
26564
26768
  }
26565
26769
  continue;
@@ -26579,7 +26783,7 @@ async function handleFlowsCommand(command, args, target, shutdown2, sessionManag
26579
26783
  );
26580
26784
  }
26581
26785
  if (backoff) {
26582
- await new Promise((resolve9) => setTimeout(resolve9, delay));
26786
+ await new Promise((resolve10) => setTimeout(resolve10, delay));
26583
26787
  delay *= 2;
26584
26788
  }
26585
26789
  }
@@ -26632,7 +26836,7 @@ async function handleFlowsCommand(command, args, target, shutdown2, sessionManag
26632
26836
  changeSummary = result.summary;
26633
26837
  break;
26634
26838
  }
26635
- await new Promise((resolve9) => setTimeout(resolve9, POLL_INTERVAL));
26839
+ await new Promise((resolve10) => setTimeout(resolve10, POLL_INTERVAL));
26636
26840
  }
26637
26841
  await page.evaluate((wid) => {
26638
26842
  const info = window[wid];
@@ -26783,13 +26987,13 @@ function parseCommandString(input) {
26783
26987
  }
26784
26988
  return parts;
26785
26989
  }
26786
- var fs25, path20, DEFAULT_MAX_FLOW_DEPTH, flowDepthMap, sessionlessSentinel;
26990
+ var fs26, path21, DEFAULT_MAX_FLOW_DEPTH, flowDepthMap, sessionlessSentinel;
26787
26991
  var init_flows = __esm({
26788
26992
  "src/commands/meta/flows.ts"() {
26789
26993
  "use strict";
26790
26994
  init_config();
26791
- fs25 = __toESM(require("fs"), 1);
26792
- path20 = __toESM(require("path"), 1);
26995
+ fs26 = __toESM(require("fs"), 1);
26996
+ path21 = __toESM(require("path"), 1);
26793
26997
  DEFAULT_MAX_FLOW_DEPTH = 10;
26794
26998
  flowDepthMap = /* @__PURE__ */ new WeakMap();
26795
26999
  sessionlessSentinel = {};
@@ -26800,7 +27004,7 @@ var init_flows = __esm({
26800
27004
  async function handleSimCommand(command, args, bm, shutdown2, sessionManager2, currentSession) {
26801
27005
  const sub = args[0];
26802
27006
  if (!sub || !["start", "stop", "status"].includes(sub)) {
26803
- throw new Error("Usage: browse sim start --platform ios|android [--device <name>] [--app <id>] [--visible] | stop | status");
27007
+ throw new Error("Usage: browse sim start --platform ios|android [--device <name>] [--app <id-or-path>] [--visible] | stop | status");
26804
27008
  }
26805
27009
  let platform = "ios";
26806
27010
  for (let i = 1; i < args.length; i++) {
@@ -27912,13 +28116,14 @@ var init_registry = __esm({
27912
28116
  inputSchema: { type: "object", properties: { json: { type: "boolean", description: "Return raw JSON instead of formatted text." } } },
27913
28117
  argDecode: (p) => p.json ? ["--json"] : []
27914
28118
  } }),
27915
- m("sim", "Simulator/emulator lifecycle", { usage: "start|stop|status [--platform ios|android] [--device <name>]", mcp: {
27916
- description: "Manage iOS simulator or Android emulator lifecycle. Start boots the simulator/emulator and launches the browse runner. Stop kills the runner and optionally shuts down the device. Status shows the current runner health.",
27917
- inputSchema: { type: "object", properties: { action: { type: "string", description: "Lifecycle action.", enum: ["start", "stop", "status"] }, platform: { type: "string", description: "Target platform.", enum: ["ios", "android"] }, device: { type: "string", description: "Device name, UDID, or serial." } }, required: ["action"] },
28119
+ m("sim", "Simulator/emulator lifecycle", { usage: "start|stop|status [--platform ios|android] [--device <name>] [--app <id-or-path>]", mcp: {
28120
+ description: "Manage iOS simulator or Android emulator lifecycle. Start boots the simulator/emulator, optionally installs an app from a file path (.app/.ipa/.apk), and launches the browse runner. Stop kills the runner and optionally shuts down the device. Status shows the current runner health.",
28121
+ inputSchema: { type: "object", properties: { action: { type: "string", description: "Lifecycle action.", enum: ["start", "stop", "status"] }, platform: { type: "string", description: "Target platform.", enum: ["ios", "android"] }, device: { type: "string", description: "Device name, UDID, or serial." }, app: { type: "string", description: "Bundle ID, package name, or path to .app/.ipa/.apk file to install and test." } }, required: ["action"] },
27918
28122
  argDecode: (p) => {
27919
28123
  const args = [String(p.action)];
27920
28124
  if (p.platform) args.push("--platform", String(p.platform));
27921
28125
  if (p.device) args.push("--device", String(p.device));
28126
+ if (p.app) args.push("--app", String(p.app));
27922
28127
  return args;
27923
28128
  }
27924
28129
  } })
@@ -28470,9 +28675,9 @@ var manager_exports2 = {};
28470
28675
  __export(manager_exports2, {
28471
28676
  AndroidAppManager: () => AndroidAppManager
28472
28677
  });
28473
- function normalizeNode(raw, path24 = []) {
28678
+ function normalizeNode(raw, path25 = []) {
28474
28679
  return {
28475
- path: path24,
28680
+ path: path25,
28476
28681
  role: androidRole(raw.className),
28477
28682
  label: raw.text ?? raw.hint ?? "",
28478
28683
  value: raw.editable ? raw.text ?? "" : void 0,
@@ -28487,7 +28692,7 @@ function normalizeNode(raw, path24 = []) {
28487
28692
  selected: raw.selected,
28488
28693
  editable: raw.editable,
28489
28694
  actions: deriveActions(raw),
28490
- children: raw.children.map((child, i) => normalizeNode(child, [...path24, i]))
28695
+ children: raw.children.map((child, i) => normalizeNode(child, [...path25, i]))
28491
28696
  };
28492
28697
  }
28493
28698
  function androidRole(className) {
@@ -28603,15 +28808,15 @@ var init_manager2 = __esm({
28603
28808
  }
28604
28809
  /** Tap (click) an element by ref */
28605
28810
  async tap(ref) {
28606
- const { path: path24, label } = this.resolveRef(ref);
28607
- const result = await this.bridge.action(path24, "click");
28811
+ const { path: path25, label } = this.resolveRef(ref);
28812
+ const result = await this.bridge.action(path25, "click");
28608
28813
  if (!result.success) throw new Error(result.error ?? "Tap failed");
28609
28814
  return `Tapped ${ref}${label ? ` "${label}"` : ""}`;
28610
28815
  }
28611
28816
  /** Fill a text field by ref */
28612
28817
  async fill(ref, value) {
28613
- const { path: path24 } = this.resolveRef(ref);
28614
- const result = await this.bridge.setValue(path24, value);
28818
+ const { path: path25 } = this.resolveRef(ref);
28819
+ const result = await this.bridge.setValue(path25, value);
28615
28820
  if (!result.success) throw new Error(result.error ?? "Fill failed");
28616
28821
  return `Filled ${ref} with "${value}"`;
28617
28822
  }
@@ -28632,8 +28837,8 @@ var init_manager2 = __esm({
28632
28837
  const actionName = actionMap[direction.toLowerCase()];
28633
28838
  if (!actionName) throw new Error(`Invalid swipe direction: ${direction}. Use up/down/left/right.`);
28634
28839
  if (ref) {
28635
- const { path: path24, label } = this.resolveRef(ref);
28636
- const result2 = await this.bridge.action(path24, actionName);
28840
+ const { path: path25, label } = this.resolveRef(ref);
28841
+ const result2 = await this.bridge.action(path25, actionName);
28637
28842
  if (!result2.success) return this.swipeBoundaryMessage(direction, result2.error);
28638
28843
  return `Swiped ${direction} on ${ref}${label ? ` "${label}"` : ""}`;
28639
28844
  }
@@ -28799,16 +29004,16 @@ var init_manager3 = __esm({
28799
29004
  /** Tap (press) an element by ref. */
28800
29005
  async tap(ref) {
28801
29006
  this.requireBridge();
28802
- const { path: path24, label } = this.resolveRef(ref);
28803
- const result = await this.bridge.action(path24, "AXPress");
29007
+ const { path: path25, label } = this.resolveRef(ref);
29008
+ const result = await this.bridge.action(path25, "AXPress");
28804
29009
  if (!result.success) throw new Error(result.error || "Tap failed");
28805
29010
  return `Tapped ${ref}${label ? ` "${label}"` : ""}`;
28806
29011
  }
28807
29012
  /** Fill a text field by ref. */
28808
29013
  async fill(ref, value) {
28809
29014
  this.requireBridge();
28810
- const { path: path24 } = this.resolveRef(ref);
28811
- const result = await this.bridge.setValue(path24, value);
29015
+ const { path: path25 } = this.resolveRef(ref);
29016
+ const result = await this.bridge.setValue(path25, value);
28812
29017
  if (!result.success) throw new Error(result.error || "Fill failed");
28813
29018
  return `Filled ${ref} with "${value}"`;
28814
29019
  }
@@ -28824,8 +29029,8 @@ var init_manager3 = __esm({
28824
29029
  this.requireBridge();
28825
29030
  const actionName = `swipe${direction.charAt(0).toUpperCase()}${direction.slice(1).toLowerCase()}`;
28826
29031
  if (ref) {
28827
- const { path: path24, label } = this.resolveRef(ref);
28828
- const result2 = await this.bridge.action(path24, actionName);
29032
+ const { path: path25, label } = this.resolveRef(ref);
29033
+ const result2 = await this.bridge.action(path25, actionName);
28829
29034
  if (!result2.success) throw new Error(result2.error || `Swipe ${direction} failed`);
28830
29035
  return `Swiped ${direction} on ${ref}${label ? ` "${label}"` : ""}`;
28831
29036
  }
@@ -28974,15 +29179,15 @@ var init_manager4 = __esm({
28974
29179
  }
28975
29180
  /** Tap (press) an element by ref */
28976
29181
  async tap(ref) {
28977
- const { path: path24, label } = this.resolveRef(ref);
28978
- const result = await this.bridge.action(path24, "AXPress");
29182
+ const { path: path25, label } = this.resolveRef(ref);
29183
+ const result = await this.bridge.action(path25, "AXPress");
28979
29184
  if (!result.success) throw new Error(result.error || "Tap failed");
28980
29185
  return `Tapped ${ref}${label ? ` "${label}"` : ""}`;
28981
29186
  }
28982
29187
  /** Fill a text field by ref */
28983
29188
  async fill(ref, value) {
28984
- const { path: path24 } = this.resolveRef(ref);
28985
- const result = await this.bridge.setValue(path24, value);
29189
+ const { path: path25 } = this.resolveRef(ref);
29190
+ const result = await this.bridge.setValue(path25, value);
28986
29191
  if (!result.success) throw new Error(result.error || "Fill failed");
28987
29192
  return `Filled ${ref} with "${value}"`;
28988
29193
  }
@@ -29003,8 +29208,8 @@ var init_manager4 = __esm({
29003
29208
  const actionName = actionMap[direction.toLowerCase()];
29004
29209
  if (!actionName) throw new Error(`Invalid swipe direction: ${direction}. Use up/down/left/right.`);
29005
29210
  if (ref) {
29006
- const { path: path24, label } = this.resolveRef(ref);
29007
- const result2 = await this.bridge.action(path24, actionName);
29211
+ const { path: path25, label } = this.resolveRef(ref);
29212
+ const result2 = await this.bridge.action(path25, actionName);
29008
29213
  if (!result2.success) throw new Error(result2.error || `Swipe ${direction} failed`);
29009
29214
  return `Swiped ${direction} on ${ref}${label ? ` "${label}"` : ""}`;
29010
29215
  }
@@ -29128,10 +29333,10 @@ function createAppTargetFactory(appName) {
29128
29333
  const { ensureMacOSBridge: ensureMacOSBridge2, createMacOSBridge: createMacOSBridge2 } = await Promise.resolve().then(() => (init_bridge3(), bridge_exports3));
29129
29334
  const { AppManager: AppManager2 } = await Promise.resolve().then(() => (init_manager4(), manager_exports4));
29130
29335
  const bridgePath = await ensureMacOSBridge2();
29131
- const { execSync: execSync7 } = await import("child_process");
29336
+ const { execSync: execSync8 } = await import("child_process");
29132
29337
  let pid;
29133
29338
  try {
29134
- const output = execSync7(`pgrep -xo "${appName}"`, { encoding: "utf-8" }).trim();
29339
+ const output = execSync8(`pgrep -xo "${appName}"`, { encoding: "utf-8" }).trim();
29135
29340
  pid = parseInt(output, 10);
29136
29341
  if (isNaN(pid)) throw new Error(`App '${appName}' is not running`);
29137
29342
  } catch (err) {
@@ -29183,7 +29388,7 @@ __export(resolver_exports, {
29183
29388
  });
29184
29389
  function findLightpanda() {
29185
29390
  try {
29186
- const result = (0, import_child_process10.execSync)("which lightpanda", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
29391
+ const result = (0, import_child_process11.execSync)("which lightpanda", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
29187
29392
  if (result) return result;
29188
29393
  } catch {
29189
29394
  }
@@ -29207,13 +29412,13 @@ async function getRuntime(name) {
29207
29412
  }
29208
29413
  return loader();
29209
29414
  }
29210
- var import_os, import_fs, import_child_process10, import_path, net2, registry2, AVAILABLE_RUNTIMES;
29415
+ var import_os, import_fs, import_child_process11, import_path, net2, registry2, AVAILABLE_RUNTIMES;
29211
29416
  var init_resolver = __esm({
29212
29417
  "src/engine/resolver.ts"() {
29213
29418
  "use strict";
29214
29419
  import_os = require("os");
29215
29420
  import_fs = require("fs");
29216
- import_child_process10 = require("child_process");
29421
+ import_child_process11 = require("child_process");
29217
29422
  import_path = require("path");
29218
29423
  net2 = __toESM(require("net"), 1);
29219
29424
  registry2 = {
@@ -29238,16 +29443,16 @@ var init_resolver = __esm({
29238
29443
  "Lightpanda not found. Install: https://lightpanda.io/docs/open-source/installation"
29239
29444
  );
29240
29445
  }
29241
- const port = await new Promise((resolve9, reject) => {
29446
+ const port = await new Promise((resolve10, reject) => {
29242
29447
  const srv = net2.createServer();
29243
29448
  srv.listen(0, "127.0.0.1", () => {
29244
29449
  const addr = srv.address();
29245
29450
  const p = typeof addr === "object" && addr ? addr.port : 0;
29246
- srv.close(() => resolve9(p));
29451
+ srv.close(() => resolve10(p));
29247
29452
  });
29248
29453
  srv.on("error", reject);
29249
29454
  });
29250
- const child = (0, import_child_process10.spawn)(
29455
+ const child = (0, import_child_process11.spawn)(
29251
29456
  binaryPath,
29252
29457
  ["serve", "--host", "127.0.0.1", "--port", String(port), "--timeout", "604800"],
29253
29458
  { stdio: ["ignore", "pipe", "pipe"] }
@@ -29603,7 +29808,7 @@ var init_domain_filter = __esm({
29603
29808
  });
29604
29809
 
29605
29810
  // src/session/manager.ts
29606
- var fs26, path21, SessionManager;
29811
+ var fs27, path22, SessionManager;
29607
29812
  var init_manager5 = __esm({
29608
29813
  "src/session/manager.ts"() {
29609
29814
  "use strict";
@@ -29612,8 +29817,8 @@ var init_manager5 = __esm({
29612
29817
  init_sanitize();
29613
29818
  init_persist();
29614
29819
  init_encryption();
29615
- fs26 = __toESM(require("fs"), 1);
29616
- path21 = __toESM(require("path"), 1);
29820
+ fs27 = __toESM(require("fs"), 1);
29821
+ path22 = __toESM(require("path"), 1);
29617
29822
  SessionManager = class {
29618
29823
  sessions = /* @__PURE__ */ new Map();
29619
29824
  /** Factory-created target accessors for setup operations that need target-specific methods */
@@ -29694,8 +29899,8 @@ var init_manager5 = __esm({
29694
29899
  }
29695
29900
  return session;
29696
29901
  }
29697
- const outputDir = path21.join(this.localDir, "sessions", sanitizeName(sessionId));
29698
- fs26.mkdirSync(outputDir, { recursive: true });
29902
+ const outputDir = path22.join(this.localDir, "sessions", sanitizeName(sessionId));
29903
+ fs27.mkdirSync(outputDir, { recursive: true });
29699
29904
  const buffers = new SessionBuffers();
29700
29905
  const effectiveFactory = this.appFactories.get(sessionId) ?? this.factory;
29701
29906
  const ct = await effectiveFactory.create(buffers, this.reuseContext && this.sessions.size === 0);
@@ -29892,7 +30097,7 @@ function flushSessionBuffers(session, final) {
29892
30097
  const lines = newEntries.map(
29893
30098
  (e) => `[${new Date(e.timestamp).toISOString()}] [${e.level}] ${e.text}`
29894
30099
  ).join("\n") + "\n";
29895
- fs27.appendFileSync(consolePath, lines);
30100
+ fs28.appendFileSync(consolePath, lines);
29896
30101
  buffers.lastConsoleFlushed = buffers.consoleTotalAdded;
29897
30102
  }
29898
30103
  let newNetworkCount = buffers.networkTotalAdded - buffers.lastNetworkFlushed;
@@ -29917,7 +30122,7 @@ function flushSessionBuffers(session, final) {
29917
30122
  const lines = prefix.map(
29918
30123
  (e) => `[${new Date(e.timestamp).toISOString()}] ${e.method} ${e.url} \u2192 ${e.status || "pending"} (${e.duration || "?"}ms, ${e.size || "?"}B)`
29919
30124
  ).join("\n") + "\n";
29920
- fs27.appendFileSync(networkPath, lines);
30125
+ fs28.appendFileSync(networkPath, lines);
29921
30126
  buffers.lastNetworkFlushed += prefixLen;
29922
30127
  }
29923
30128
  }
@@ -29929,11 +30134,11 @@ function trySessionBt(session) {
29929
30134
  return null;
29930
30135
  }
29931
30136
  function isPortFree2(port) {
29932
- return new Promise((resolve9) => {
30137
+ return new Promise((resolve10) => {
29933
30138
  const srv = net3.createServer();
29934
- srv.once("error", () => resolve9(false));
30139
+ srv.once("error", () => resolve10(false));
29935
30140
  srv.once("listening", () => {
29936
- srv.close(() => resolve9(true));
30141
+ srv.close(() => resolve10(true));
29937
30142
  });
29938
30143
  srv.listen(port, "127.0.0.1");
29939
30144
  });
@@ -30136,9 +30341,9 @@ async function shutdown() {
30136
30341
  await activeRuntime?.close?.().catch(() => {
30137
30342
  });
30138
30343
  try {
30139
- const currentState = JSON.parse(fs27.readFileSync(STATE_FILE, "utf-8"));
30344
+ const currentState = JSON.parse(fs28.readFileSync(STATE_FILE, "utf-8"));
30140
30345
  if (currentState.pid === process.pid || currentState.token === AUTH_TOKEN) {
30141
- fs27.unlinkSync(STATE_FILE);
30346
+ fs28.unlinkSync(STATE_FILE);
30142
30347
  }
30143
30348
  } catch {
30144
30349
  }
@@ -30156,14 +30361,14 @@ async function start() {
30156
30361
  const { SessionBuffers: SessionBuffers2 } = await Promise.resolve().then(() => (init_buffers(), buffers_exports));
30157
30362
  const { createPersistentBrowserTarget: createPersistentBrowserTarget2 } = await Promise.resolve().then(() => (init_target_factory(), target_factory_exports));
30158
30363
  const profileDir = getProfileDir2(LOCAL_DIR7, profileName);
30159
- fs27.mkdirSync(profileDir, { recursive: true });
30364
+ fs28.mkdirSync(profileDir, { recursive: true });
30160
30365
  const profileTarget = await createPersistentBrowserTarget2(profileDir, () => {
30161
30366
  if (isShuttingDown) return;
30162
30367
  console.error("[browse] Chromium disconnected (profile mode). Shutting down.");
30163
30368
  shutdown();
30164
30369
  });
30165
- const outputDir = path22.join(LOCAL_DIR7, "sessions", profileName);
30166
- fs27.mkdirSync(outputDir, { recursive: true });
30370
+ const outputDir = path23.join(LOCAL_DIR7, "sessions", profileName);
30371
+ fs28.mkdirSync(outputDir, { recursive: true });
30167
30372
  profileSession = {
30168
30373
  id: profileName,
30169
30374
  manager: profileTarget.target,
@@ -30305,7 +30510,7 @@ async function start() {
30305
30510
  const context = sessionBt_?.getContext();
30306
30511
  if (context) {
30307
30512
  try {
30308
- const stateData = JSON.parse(fs27.readFileSync(stateFilePath3, "utf-8"));
30513
+ const stateData = JSON.parse(fs28.readFileSync(stateFilePath3, "utf-8"));
30309
30514
  if (stateData.cookies?.length) {
30310
30515
  await context.addCookies(stateData.cookies);
30311
30516
  }
@@ -30341,7 +30546,7 @@ async function start() {
30341
30546
  port,
30342
30547
  token: AUTH_TOKEN,
30343
30548
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
30344
- serverPath: path22.resolve(path22.dirname((0, import_url7.fileURLToPath)(__import_meta_url)), "server.ts")
30549
+ serverPath: path23.resolve(path23.dirname((0, import_url7.fileURLToPath)(__import_meta_url)), "server.ts")
30345
30550
  };
30346
30551
  if (profileName) {
30347
30552
  state.profile = profileName;
@@ -30349,12 +30554,12 @@ async function start() {
30349
30554
  if (DEBUG_PORT > 0) {
30350
30555
  state.debugPort = DEBUG_PORT;
30351
30556
  }
30352
- fs27.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2), { mode: 384 });
30557
+ fs28.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2), { mode: 384 });
30353
30558
  console.log(`[browse] Server running on http://127.0.0.1:${port} (PID: ${process.pid})`);
30354
30559
  console.log(`[browse] State file: ${STATE_FILE}`);
30355
30560
  console.log(`[browse] Idle timeout: ${IDLE_TIMEOUT_MS / 1e3}s`);
30356
30561
  }
30357
- var fs27, path22, crypto3, http, import_url7, net3, AUTH_TOKEN, DEBUG_PORT, BROWSE_PORT, BROWSE_INSTANCE, INSTANCE_SUFFIX, LOCAL_DIR7, STATE_FILE, IDLE_TIMEOUT_MS, sessionManager, browser, profileSession, activeRuntime, isShuttingDown, isRemoteBrowser, policyChecker, BOUNDARY_NONCE, rewriteError2, flushInterval, sessionCleanupInterval;
30562
+ var fs28, path23, crypto3, http, import_url7, net3, AUTH_TOKEN, DEBUG_PORT, BROWSE_PORT, BROWSE_INSTANCE, INSTANCE_SUFFIX, LOCAL_DIR7, STATE_FILE, IDLE_TIMEOUT_MS, sessionManager, browser, profileSession, activeRuntime, isShuttingDown, isRemoteBrowser, policyChecker, BOUNDARY_NONCE, rewriteError2, flushInterval, sessionCleanupInterval;
30358
30563
  var init_server2 = __esm({
30359
30564
  "src/server.ts"() {
30360
30565
  "use strict";
@@ -30364,8 +30569,8 @@ var init_server2 = __esm({
30364
30569
  init_constants();
30365
30570
  init_action_context();
30366
30571
  init_executor();
30367
- fs27 = __toESM(require("fs"), 1);
30368
- path22 = __toESM(require("path"), 1);
30572
+ fs28 = __toESM(require("fs"), 1);
30573
+ path23 = __toESM(require("path"), 1);
30369
30574
  crypto3 = __toESM(require("crypto"), 1);
30370
30575
  http = __toESM(require("http"), 1);
30371
30576
  import_url7 = require("url");
@@ -30427,9 +30632,9 @@ __export(cli_exports, {
30427
30632
  resolveServerScript: () => resolveServerScript
30428
30633
  });
30429
30634
  module.exports = __toCommonJS(cli_exports);
30430
- var fs28 = __toESM(require("fs"), 1);
30431
- var path23 = __toESM(require("path"), 1);
30432
- var import_child_process11 = require("child_process");
30635
+ var fs29 = __toESM(require("fs"), 1);
30636
+ var path24 = __toESM(require("path"), 1);
30637
+ var import_child_process12 = require("child_process");
30433
30638
  var import_url8 = require("url");
30434
30639
  init_constants();
30435
30640
  init_config();
@@ -30463,50 +30668,50 @@ var INSTANCE_SUFFIX2 = BROWSE_PORT2 ? `-${BROWSE_PORT2}` : BROWSE_INSTANCE2 ? `-
30463
30668
  function resolveLocalDir() {
30464
30669
  if (process.env.BROWSE_LOCAL_DIR) {
30465
30670
  try {
30466
- fs28.mkdirSync(process.env.BROWSE_LOCAL_DIR, { recursive: true });
30671
+ fs29.mkdirSync(process.env.BROWSE_LOCAL_DIR, { recursive: true });
30467
30672
  } catch {
30468
30673
  }
30469
30674
  return process.env.BROWSE_LOCAL_DIR;
30470
30675
  }
30471
30676
  let dir = process.cwd();
30472
30677
  for (let i = 0; i < 20; i++) {
30473
- if (fs28.existsSync(path23.join(dir, ".git")) || fs28.existsSync(path23.join(dir, ".claude"))) {
30474
- const browseDir = path23.join(dir, ".browse");
30678
+ if (fs29.existsSync(path24.join(dir, ".git")) || fs29.existsSync(path24.join(dir, ".claude"))) {
30679
+ const browseDir = path24.join(dir, ".browse");
30475
30680
  try {
30476
- fs28.mkdirSync(browseDir, { recursive: true });
30477
- const gi = path23.join(browseDir, ".gitignore");
30478
- if (!fs28.existsSync(gi)) {
30479
- fs28.writeFileSync(gi, "*\n");
30681
+ fs29.mkdirSync(browseDir, { recursive: true });
30682
+ const gi = path24.join(browseDir, ".gitignore");
30683
+ if (!fs29.existsSync(gi)) {
30684
+ fs29.writeFileSync(gi, "*\n");
30480
30685
  }
30481
30686
  } catch {
30482
30687
  }
30483
30688
  return browseDir;
30484
30689
  }
30485
- const parent = path23.dirname(dir);
30690
+ const parent = path24.dirname(dir);
30486
30691
  if (parent === dir) break;
30487
30692
  dir = parent;
30488
30693
  }
30489
30694
  return "/tmp";
30490
30695
  }
30491
30696
  var LOCAL_DIR8 = resolveLocalDir();
30492
- var STATE_FILE2 = process.env.BROWSE_STATE_FILE || path23.join(LOCAL_DIR8, `browse-server${INSTANCE_SUFFIX2}.json`);
30697
+ var STATE_FILE2 = process.env.BROWSE_STATE_FILE || path24.join(LOCAL_DIR8, `browse-server${INSTANCE_SUFFIX2}.json`);
30493
30698
  var MAX_START_WAIT = parseInt(process.env.BROWSE_START_TIMEOUT || "0", 10) || 8e3;
30494
30699
  var LOCK_FILE = STATE_FILE2 + ".lock";
30495
30700
  var LOCK_STALE_MS = DEFAULTS.LOCK_STALE_THRESHOLD_MS;
30496
30701
  var __filename_cli = (0, import_url8.fileURLToPath)(__import_meta_url);
30497
- var __dirname_cli = path23.dirname(__filename_cli);
30702
+ var __dirname_cli = path24.dirname(__filename_cli);
30498
30703
  function resolveServerScript(env = process.env, metaDir = __dirname_cli) {
30499
30704
  if (env.BROWSE_SERVER_SCRIPT) {
30500
30705
  return env.BROWSE_SERVER_SCRIPT;
30501
30706
  }
30502
30707
  if (metaDir.startsWith("/")) {
30503
- const direct = path23.resolve(metaDir, "server.ts");
30504
- if (fs28.existsSync(direct)) {
30708
+ const direct = path24.resolve(metaDir, "server.ts");
30709
+ if (fs29.existsSync(direct)) {
30505
30710
  return direct;
30506
30711
  }
30507
30712
  }
30508
30713
  const selfPath = (0, import_url8.fileURLToPath)(__import_meta_url);
30509
- if (fs28.existsSync(selfPath)) {
30714
+ if (fs29.existsSync(selfPath)) {
30510
30715
  return "__self__";
30511
30716
  }
30512
30717
  throw new Error(
@@ -30516,7 +30721,7 @@ function resolveServerScript(env = process.env, metaDir = __dirname_cli) {
30516
30721
  var SERVER_SCRIPT = resolveServerScript();
30517
30722
  function readState3() {
30518
30723
  try {
30519
- const data = fs28.readFileSync(STATE_FILE2, "utf-8");
30724
+ const data = fs29.readFileSync(STATE_FILE2, "utf-8");
30520
30725
  return JSON.parse(data);
30521
30726
  } catch {
30522
30727
  return null;
@@ -30532,7 +30737,7 @@ function isProcessAlive(pid) {
30532
30737
  }
30533
30738
  async function listInstances() {
30534
30739
  try {
30535
- const files = fs28.readdirSync(LOCAL_DIR8).filter(
30740
+ const files = fs29.readdirSync(LOCAL_DIR8).filter(
30536
30741
  (f) => f.startsWith("browse-server") && f.endsWith(".json") && !f.endsWith(".lock")
30537
30742
  );
30538
30743
  if (files.length === 0) {
@@ -30542,7 +30747,7 @@ async function listInstances() {
30542
30747
  let found = false;
30543
30748
  for (const file of files) {
30544
30749
  try {
30545
- const data = JSON.parse(fs28.readFileSync(path23.join(LOCAL_DIR8, file), "utf-8"));
30750
+ const data = JSON.parse(fs29.readFileSync(path24.join(LOCAL_DIR8, file), "utf-8"));
30546
30751
  if (!data.pid || !data.port) continue;
30547
30752
  const alive = isProcessAlive(data.pid);
30548
30753
  let status3 = "dead";
@@ -30565,7 +30770,7 @@ async function listInstances() {
30565
30770
  found = true;
30566
30771
  if (!alive) {
30567
30772
  try {
30568
- fs28.unlinkSync(path23.join(LOCAL_DIR8, file));
30773
+ fs29.unlinkSync(path24.join(LOCAL_DIR8, file));
30569
30774
  } catch {
30570
30775
  }
30571
30776
  }
@@ -30579,8 +30784,8 @@ async function listInstances() {
30579
30784
  }
30580
30785
  function isBrowseProcess(pid) {
30581
30786
  try {
30582
- const { execSync: execSync7 } = require("child_process");
30583
- const cmd = execSync7(`ps -p ${pid} -o command=`, { encoding: "utf-8" }).trim();
30787
+ const { execSync: execSync8 } = require("child_process");
30788
+ const cmd = execSync8(`ps -p ${pid} -o command=`, { encoding: "utf-8" }).trim();
30584
30789
  return cmd.includes("browse") || cmd.includes("__BROWSE_SERVER_MODE");
30585
30790
  } catch {
30586
30791
  return false;
@@ -30588,15 +30793,15 @@ function isBrowseProcess(pid) {
30588
30793
  }
30589
30794
  function acquireLock() {
30590
30795
  try {
30591
- fs28.writeFileSync(LOCK_FILE, String(process.pid), { flag: "wx" });
30796
+ fs29.writeFileSync(LOCK_FILE, String(process.pid), { flag: "wx" });
30592
30797
  return true;
30593
30798
  } catch (err) {
30594
30799
  if (err.code === "EEXIST") {
30595
30800
  try {
30596
- const stat = fs28.statSync(LOCK_FILE);
30801
+ const stat = fs29.statSync(LOCK_FILE);
30597
30802
  if (Date.now() - stat.mtimeMs > LOCK_STALE_MS) {
30598
30803
  try {
30599
- fs28.unlinkSync(LOCK_FILE);
30804
+ fs29.unlinkSync(LOCK_FILE);
30600
30805
  } catch {
30601
30806
  }
30602
30807
  return acquireLock();
@@ -30610,7 +30815,7 @@ function acquireLock() {
30610
30815
  }
30611
30816
  function releaseLock() {
30612
30817
  try {
30613
- fs28.unlinkSync(LOCK_FILE);
30818
+ fs29.unlinkSync(LOCK_FILE);
30614
30819
  } catch {
30615
30820
  }
30616
30821
  }
@@ -30627,7 +30832,7 @@ async function startServer() {
30627
30832
  }
30628
30833
  await sleep3(100);
30629
30834
  }
30630
- if (!fs28.existsSync(LOCK_FILE) || fs28.readFileSync(LOCK_FILE, "utf-8").trim() !== String(process.pid)) {
30835
+ if (!fs29.existsSync(LOCK_FILE) || fs29.readFileSync(LOCK_FILE, "utf-8").trim() !== String(process.pid)) {
30631
30836
  const state = readState3();
30632
30837
  if (state && isProcessAlive(state.pid)) return state;
30633
30838
  throw new Error("Server failed to start (another process is starting it)");
@@ -30637,7 +30842,7 @@ async function startServer() {
30637
30842
  try {
30638
30843
  const oldState = readState3();
30639
30844
  if (oldState && !isProcessAlive(oldState.pid)) {
30640
- fs28.unlinkSync(STATE_FILE2);
30845
+ fs29.unlinkSync(STATE_FILE2);
30641
30846
  }
30642
30847
  } catch {
30643
30848
  }
@@ -30646,7 +30851,7 @@ async function startServer() {
30646
30851
  const spawnCmd = SERVER_SCRIPT === "__self__" ? [nodeExec, selfPath] : [nodeExec, "--import", "tsx", SERVER_SCRIPT];
30647
30852
  const startTimeout = cliFlags.runtime === "chrome" ? String(DEFAULTS.CHROME_CDP_TIMEOUT_MS + 5e3) : "";
30648
30853
  const spawnEnv = { ...process.env, __BROWSE_SERVER_MODE: "1", BROWSE_LOCAL_DIR: LOCAL_DIR8, BROWSE_INSTANCE: BROWSE_INSTANCE2, ...cliFlags.headed ? { BROWSE_HEADED: "1" } : {}, ...cliFlags.cdpUrl ? { BROWSE_CDP_URL: cliFlags.cdpUrl } : {}, ...cliFlags.profile ? { BROWSE_PROFILE: cliFlags.profile } : {}, ...cliFlags.runtime ? { BROWSE_RUNTIME: cliFlags.runtime } : {}, ...startTimeout ? { BROWSE_START_TIMEOUT: startTimeout } : {} };
30649
- const proc = (0, import_child_process11.spawn)(spawnCmd[0], spawnCmd.slice(1), {
30854
+ const proc = (0, import_child_process12.spawn)(spawnCmd[0], spawnCmd.slice(1), {
30650
30855
  stdio: ["ignore", "ignore", "pipe"],
30651
30856
  env: spawnEnv,
30652
30857
  detached: true
@@ -30723,7 +30928,7 @@ async function ensureServer() {
30723
30928
  }
30724
30929
  if (state) {
30725
30930
  try {
30726
- fs28.unlinkSync(STATE_FILE2);
30931
+ fs29.unlinkSync(STATE_FILE2);
30727
30932
  } catch {
30728
30933
  }
30729
30934
  }
@@ -30733,21 +30938,21 @@ async function ensureServer() {
30733
30938
  }
30734
30939
  function cleanOrphanedServers() {
30735
30940
  try {
30736
- const files = fs28.readdirSync(LOCAL_DIR8);
30941
+ const files = fs29.readdirSync(LOCAL_DIR8);
30737
30942
  for (const file of files) {
30738
30943
  if (!file.startsWith("browse-server") || !file.endsWith(".json") || file.endsWith(".lock")) continue;
30739
- const filePath = path23.join(LOCAL_DIR8, file);
30944
+ const filePath = path24.join(LOCAL_DIR8, file);
30740
30945
  if (filePath === STATE_FILE2) continue;
30741
30946
  try {
30742
- const data = JSON.parse(fs28.readFileSync(filePath, "utf-8"));
30947
+ const data = JSON.parse(fs29.readFileSync(filePath, "utf-8"));
30743
30948
  if (!data.pid) {
30744
- fs28.unlinkSync(filePath);
30949
+ fs29.unlinkSync(filePath);
30745
30950
  continue;
30746
30951
  }
30747
30952
  const suffixMatch = file.match(/browse-server-(\d+)\.json$/);
30748
30953
  if (suffixMatch && data.port === parseInt(suffixMatch[1], 10) && isProcessAlive(data.pid)) continue;
30749
30954
  if (!isProcessAlive(data.pid)) {
30750
- fs28.unlinkSync(filePath);
30955
+ fs29.unlinkSync(filePath);
30751
30956
  continue;
30752
30957
  }
30753
30958
  if (isBrowseProcess(data.pid)) {
@@ -30758,7 +30963,7 @@ function cleanOrphanedServers() {
30758
30963
  }
30759
30964
  } catch {
30760
30965
  try {
30761
- fs28.unlinkSync(filePath);
30966
+ fs29.unlinkSync(filePath);
30762
30967
  } catch {
30763
30968
  }
30764
30969
  }
@@ -30878,7 +31083,7 @@ async function sendCommand(state, command, args, retries = 0, sessionId) {
30878
31083
  await sleep3(300);
30879
31084
  }
30880
31085
  try {
30881
- fs28.unlinkSync(STATE_FILE2);
31086
+ fs29.unlinkSync(STATE_FILE2);
30882
31087
  } catch {
30883
31088
  }
30884
31089
  if (command === "restart") {
@@ -31239,7 +31444,7 @@ if (process.argv.includes("--mcp")) {
31239
31444
  Promise.resolve().then(() => (init_mcp(), mcp_exports)).then((m2) => m2.startMcpServer(jsonMode));
31240
31445
  } else if (process.env.__BROWSE_SERVER_MODE === "1") {
31241
31446
  Promise.resolve().then(() => init_server2());
31242
- } else if (process.argv[1] && fs28.realpathSync(process.argv[1]) === fs28.realpathSync(__filename_cli)) {
31447
+ } else if (process.argv[1] && fs29.realpathSync(process.argv[1]) === fs29.realpathSync(__filename_cli)) {
31243
31448
  main().catch((err) => {
31244
31449
  console.error(`[browse] ${err.message}`);
31245
31450
  process.exit(1);