rnxsim 0.1.313 → 0.1.315

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (297) hide show
  1. package/LICENSE +11 -10
  2. package/README.md +5 -0
  3. package/cli/app-config.ts +65 -0
  4. package/cli/app-fonts.ts +408 -0
  5. package/cli/app-project.ts +231 -0
  6. package/cli/app-splash.ts +185 -0
  7. package/cli/app-state-reset.ts +24 -0
  8. package/cli/auth.ts +155 -0
  9. package/cli/bin.ts +594 -0
  10. package/cli/bridge-diagnostics.ts +226 -0
  11. package/cli/bridge-flow-runner.ts +2830 -0
  12. package/cli/browser-evals.ts +96 -0
  13. package/cli/commands/agent-wrapper.ts +986 -0
  14. package/cli/commands/agent.ts +423 -0
  15. package/cli/commands/app-fonts.ts +98 -0
  16. package/cli/commands/assert.ts +541 -0
  17. package/cli/commands/auth.ts +59 -0
  18. package/cli/commands/camera.ts +266 -0
  19. package/cli/commands/cleanup.ts +169 -0
  20. package/cli/commands/compat.ts +87 -0
  21. package/cli/commands/config.ts +32 -0
  22. package/cli/commands/control.ts +2142 -0
  23. package/cli/commands/cpu-profile.ts +269 -0
  24. package/cli/commands/daemon-mac-app.ts +169 -0
  25. package/cli/commands/daemon.ts +874 -0
  26. package/cli/commands/debug.ts +719 -0
  27. package/cli/commands/desktop.ts +39 -0
  28. package/cli/commands/detect.ts +197 -0
  29. package/cli/commands/detox.ts +385 -0
  30. package/cli/commands/device.ts +133 -0
  31. package/cli/commands/diagnose.ts +589 -0
  32. package/cli/commands/electron.ts +95 -0
  33. package/cli/commands/film.ts +379 -0
  34. package/cli/commands/flow.ts +1124 -0
  35. package/cli/commands/inspect/actions.ts +622 -0
  36. package/cli/commands/inspect/core.ts +2405 -0
  37. package/cli/commands/inspect/count.ts +17 -0
  38. package/cli/commands/inspect/describe.ts +192 -0
  39. package/cli/commands/inspect/env.ts +23 -0
  40. package/cli/commands/inspect/find.ts +171 -0
  41. package/cli/commands/inspect/get-layout.ts +39 -0
  42. package/cli/commands/inspect/keyboard.ts +52 -0
  43. package/cli/commands/inspect/list.ts +58 -0
  44. package/cli/commands/inspect/memory.ts +215 -0
  45. package/cli/commands/inspect/redaction.ts +39 -0
  46. package/cli/commands/inspect/resolve-target.ts +82 -0
  47. package/cli/commands/inspect/screens.ts +78 -0
  48. package/cli/commands/inspect/settle.ts +22 -0
  49. package/cli/commands/inspect/settling.ts +158 -0
  50. package/cli/commands/inspect/shared.ts +353 -0
  51. package/cli/commands/inspect/sleep.ts +14 -0
  52. package/cli/commands/inspect/tree.ts +32 -0
  53. package/cli/commands/inspect/url.ts +17 -0
  54. package/cli/commands/inspect/wait-event.ts +210 -0
  55. package/cli/commands/inspect/wait-idle.ts +24 -0
  56. package/cli/commands/inspect/wait-ready.ts +74 -0
  57. package/cli/commands/inspect/wait-selector.ts +54 -0
  58. package/cli/commands/inspect/wait.ts +31 -0
  59. package/cli/commands/inspect.ts +4519 -0
  60. package/cli/commands/install-desktop.ts +351 -0
  61. package/cli/commands/login.ts +331 -0
  62. package/cli/commands/logout.ts +31 -0
  63. package/cli/commands/maestro-generate.ts +361 -0
  64. package/cli/commands/maestro.ts +453 -0
  65. package/cli/commands/mode.ts +57 -0
  66. package/cli/commands/no-bridge-hint.ts +80 -0
  67. package/cli/commands/perf.ts +66 -0
  68. package/cli/commands/permissions.ts +203 -0
  69. package/cli/commands/profile.ts +108 -0
  70. package/cli/commands/react.ts +353 -0
  71. package/cli/commands/record.ts +1434 -0
  72. package/cli/commands/report-issue.ts +305 -0
  73. package/cli/commands/reset.ts +85 -0
  74. package/cli/commands/runtime.ts +351 -0
  75. package/cli/commands/screenshot-command.ts +106 -0
  76. package/cli/commands/screenshot-layers.ts +143 -0
  77. package/cli/commands/screenshot-mode.ts +37 -0
  78. package/cli/commands/screenshot.ts +488 -0
  79. package/cli/commands/screenshots-capture.ts +607 -0
  80. package/cli/commands/screenshots.ts +127 -0
  81. package/cli/commands/serve.ts +168 -0
  82. package/cli/commands/setup.ts +545 -0
  83. package/cli/commands/shell-boolean-mode.ts +81 -0
  84. package/cli/commands/skills.ts +467 -0
  85. package/cli/commands/slides.ts +361 -0
  86. package/cli/commands/state.ts +87 -0
  87. package/cli/commands/storage.ts +58 -0
  88. package/cli/commands/telemetry.ts +54 -0
  89. package/cli/commands/three-mode.ts +763 -0
  90. package/cli/commands/timeline.ts +122 -0
  91. package/cli/commands/upgrade.ts +208 -0
  92. package/cli/commands/upload.ts +1225 -0
  93. package/cli/commands/version.ts +54 -0
  94. package/cli/commands/what-happened.ts +327 -0
  95. package/cli/current-sim.ts +204 -0
  96. package/cli/desktop-companion.ts +300 -0
  97. package/cli/drivers/electron.ts +70 -0
  98. package/cli/drivers/index.ts +20 -0
  99. package/cli/drivers/playwright-provisioning.ts +180 -0
  100. package/cli/drivers/playwright.ts +698 -0
  101. package/cli/drivers/registry.ts +65 -0
  102. package/cli/drivers/types.ts +102 -0
  103. package/cli/flow-file.ts +142 -0
  104. package/cli/flow-live-status.ts +120 -0
  105. package/cli/flow-session.ts +187 -0
  106. package/cli/help.ts +80 -0
  107. package/cli/hidden-runtime-alias.ts +19 -0
  108. package/cli/hints.ts +216 -0
  109. package/cli/inspect-notice-state.ts +114 -0
  110. package/cli/maestro-js.ts +334 -0
  111. package/cli/open-url.ts +8 -0
  112. package/cli/parent-pid.ts +204 -0
  113. package/cli/parse-args.ts +211 -0
  114. package/cli/prompt.ts +51 -0
  115. package/cli/recording-access.ts +107 -0
  116. package/cli/registry.ts +1 -0
  117. package/cli/resolve-assets.ts +63 -0
  118. package/cli/run-registry.ts +226 -0
  119. package/cli/runtime-notes.ts +66 -0
  120. package/cli/runtime-summary.ts +25 -0
  121. package/cli/setup-repository.ts +187 -0
  122. package/cli/telemetry.ts +187 -0
  123. package/cli/ws-bridge.ts +798 -0
  124. package/dist-cli/bin.js +5 -5
  125. package/dist-cli/chunks/{agent-XZ2KTPCU.js → agent-CBU2WDKV.js} +2 -2
  126. package/dist-cli/chunks/{agent-wrapper-JJYYW2WH.js → agent-wrapper-UNB4R27L.js} +2 -2
  127. package/dist-cli/chunks/{app-fonts-IXRNQG6B.js → app-fonts-YMLRF3JD.js} +2 -2
  128. package/dist-cli/chunks/{assert-54T5SK5F.js → assert-5N6HUCL5.js} +2 -2
  129. package/dist-cli/chunks/{auth-FI5UDI45.js → auth-BHDR3A6G.js} +2 -2
  130. package/dist-cli/chunks/{beta-JV6UKADW.js → beta-LWUVCPYK.js} +2 -2
  131. package/dist-cli/chunks/camera-CNO6B2JZ.js +33 -0
  132. package/dist-cli/chunks/{chunk-ZMJD5GEC.js → chunk-25VCR44B.js} +1 -1
  133. package/dist-cli/chunks/{chunk-WUSWBCWA.js → chunk-26CSIR76.js} +8 -9
  134. package/dist-cli/chunks/{chunk-WINYQ44O.js → chunk-26WMWUH4.js} +1 -1
  135. package/dist-cli/chunks/{chunk-BTWORNNG.js → chunk-2JZ53D4A.js} +1 -1
  136. package/dist-cli/chunks/{chunk-DCEMHR2Y.js → chunk-2POODCTW.js} +2 -2
  137. package/dist-cli/chunks/{chunk-5TEF3ET3.js → chunk-4LTBK3ZN.js} +2 -2
  138. package/dist-cli/chunks/{chunk-NMF2ZMZQ.js → chunk-54MIRTFE.js} +4 -4
  139. package/dist-cli/chunks/{chunk-WEXDAC74.js → chunk-56H6EVO4.js} +2 -2
  140. package/dist-cli/chunks/{chunk-F5ZRSS3C.js → chunk-657SQG4D.js} +1 -1
  141. package/dist-cli/chunks/{chunk-IJ5CAZZC.js → chunk-6ZMRJPKI.js} +1 -1
  142. package/dist-cli/chunks/{chunk-W6K4EFPH.js → chunk-AOGGOC4V.js} +2 -2
  143. package/dist-cli/chunks/chunk-BPCDO7ST.js +15 -0
  144. package/dist-cli/chunks/{chunk-D4FFVGI5.js → chunk-C5O5CTXJ.js} +1 -1
  145. package/dist-cli/chunks/{chunk-RTN5C5RL.js → chunk-CMM64PPR.js} +1 -1
  146. package/dist-cli/chunks/{chunk-BBULZ7CG.js → chunk-D3M36D7O.js} +62 -87
  147. package/dist-cli/chunks/{chunk-5DHC6KHQ.js → chunk-DNCNFZ7V.js} +1 -1
  148. package/dist-cli/chunks/{chunk-YIFT42WN.js → chunk-FNTTIR2P.js} +2 -2
  149. package/dist-cli/chunks/chunk-H362IX2I.js +4 -0
  150. package/dist-cli/chunks/{chunk-IJO63TDP.js → chunk-J6HMTUZH.js} +2 -2
  151. package/dist-cli/chunks/{chunk-RSZWCKNT.js → chunk-JZ6VTZ5B.js} +3 -3
  152. package/dist-cli/chunks/{chunk-MJRLLB4R.js → chunk-KQSJVNFE.js} +4 -4
  153. package/dist-cli/chunks/chunk-L4JZKVCC.js +6 -0
  154. package/dist-cli/chunks/{chunk-VFCMSYZK.js → chunk-LP2BTEJ6.js} +2 -2
  155. package/dist-cli/chunks/{chunk-2YR5BGA5.js → chunk-LT6UC7I3.js} +2 -2
  156. package/dist-cli/chunks/{chunk-OVFJFXUD.js → chunk-MCKQH4AM.js} +2 -2
  157. package/dist-cli/chunks/{chunk-TZFFR3SD.js → chunk-MF7WUHQ3.js} +2 -2
  158. package/dist-cli/chunks/{chunk-46EUUFJ5.js → chunk-MSSY4EEZ.js} +1 -1
  159. package/dist-cli/chunks/{chunk-WMIIKMGK.js → chunk-N5ZCYP5F.js} +2 -2
  160. package/dist-cli/chunks/{chunk-3NV2NCNX.js → chunk-NCKAD3V7.js} +2 -2
  161. package/dist-cli/chunks/{chunk-VNQEB4L7.js → chunk-O2KRHVTU.js} +2 -2
  162. package/dist-cli/chunks/{chunk-OZSSI4WN.js → chunk-OF2HTH25.js} +2 -2
  163. package/dist-cli/chunks/{chunk-GGRX24GF.js → chunk-OXS2IWO2.js} +2 -2
  164. package/dist-cli/chunks/{chunk-WWZIXIRD.js → chunk-PVL7XSPN.js} +1 -1
  165. package/dist-cli/chunks/{chunk-DZS6WPUI.js → chunk-PVPAP3TV.js} +1 -1
  166. package/dist-cli/chunks/{chunk-5YJCOWCH.js → chunk-PXNAZYGV.js} +1 -1
  167. package/dist-cli/chunks/chunk-QR3HTFRB.js +5 -0
  168. package/dist-cli/chunks/{chunk-5TPRP5QT.js → chunk-QR7MWAWE.js} +1 -1
  169. package/dist-cli/chunks/chunk-RJIEZ2NY.js +4 -0
  170. package/dist-cli/chunks/{chunk-UC6U3MML.js → chunk-RVM4EZ4E.js} +2 -2
  171. package/dist-cli/chunks/{chunk-2D2UPBBR.js → chunk-SZKSFMWS.js} +1 -1
  172. package/dist-cli/chunks/{chunk-7GN3LVWB.js → chunk-TEL7G234.js} +2 -2
  173. package/dist-cli/chunks/{chunk-GASE6UBA.js → chunk-U2HNIHBY.js} +1 -1
  174. package/dist-cli/chunks/chunk-UROXJVY3.js +27 -0
  175. package/dist-cli/chunks/{chunk-HI5TFJWN.js → chunk-WM32MGTJ.js} +2 -2
  176. package/dist-cli/chunks/{chunk-VZXWHRUZ.js → chunk-WMZ7NJAO.js} +89 -133
  177. package/dist-cli/chunks/{chunk-XEVZYVIW.js → chunk-WUXCH6WN.js} +10 -9
  178. package/dist-cli/chunks/chunk-XOXGC3EH.js +9 -0
  179. package/dist-cli/chunks/chunk-XSSHENNY.js +4 -0
  180. package/dist-cli/chunks/{chunk-OHAZNXLK.js → chunk-XZ6O67AR.js} +1 -1
  181. package/dist-cli/chunks/{chunk-QKDWYITG.js → chunk-Y2MWQKOS.js} +3 -3
  182. package/dist-cli/chunks/{chunk-YDGQTMQL.js → chunk-YY24QI25.js} +1 -1
  183. package/dist-cli/chunks/{chunk-WF3T4SVI.js → chunk-ZWX6DDIS.js} +2 -2
  184. package/dist-cli/chunks/{cleanup-P27PA6JI.js → cleanup-F3NUJ3SL.js} +2 -2
  185. package/dist-cli/chunks/cli-version-XIW6O3FG.js +4 -0
  186. package/dist-cli/chunks/{compat-ZD65FED3.js → compat-AX2SEPTH.js} +2 -2
  187. package/dist-cli/chunks/{config-XMJRNM2A.js → config-4QQO5JVU.js} +2 -2
  188. package/dist-cli/chunks/{control-KMIQT3QP.js → control-OCKKSOWA.js} +2 -2
  189. package/dist-cli/chunks/daemon-N5VLMYKQ.js +4 -0
  190. package/dist-cli/chunks/{debug-PT4HOP7N.js → debug-T6MWVEVX.js} +5 -5
  191. package/dist-cli/chunks/{desktop-S3FG72AK.js → desktop-NCW7R7CB.js} +3 -3
  192. package/dist-cli/chunks/{detox-B3D4IFCN.js → detox-G6GITO2X.js} +2 -2
  193. package/dist-cli/chunks/{device-XBNDSB2R.js → device-7BTEJCTH.js} +2 -2
  194. package/dist-cli/chunks/{diagnose-HMQXJE5N.js → diagnose-SC5LACE3.js} +2 -2
  195. package/dist-cli/chunks/{disk-cleanup-BLCZ5BSZ.js → disk-cleanup-KK56QQZA.js} +2 -2
  196. package/dist-cli/chunks/drivers-NPGVBWMG.js +4 -0
  197. package/dist-cli/chunks/{film-BJGTBYZB.js → film-WGYEHGIA.js} +3 -3
  198. package/dist-cli/chunks/flow-KQBIBM3N.js +4 -0
  199. package/dist-cli/chunks/help-G7CAKI65.js +4 -0
  200. package/dist-cli/chunks/{hidden-runtime-alias-ANOYADHM.js → hidden-runtime-alias-CWTZWH3L.js} +2 -2
  201. package/dist-cli/chunks/home-paths-EBT4XHAS.js +4 -0
  202. package/dist-cli/chunks/inspect-CCPZXVDU.js +4 -0
  203. package/dist-cli/chunks/install-desktop-NZ3VGTQZ.js +4 -0
  204. package/dist-cli/chunks/{login-FJ737MWG.js → login-GSYOTGVQ.js} +4 -4
  205. package/dist-cli/chunks/{logout-ZCNMMHMY.js → logout-JPRIR7YK.js} +2 -2
  206. package/dist-cli/chunks/{maestro-SZTNKLDF.js → maestro-LXKDNO5M.js} +3 -3
  207. package/dist-cli/chunks/{maestro-generate-DCFAIZ4H.js → maestro-generate-5ZIKOU53.js} +3 -3
  208. package/dist-cli/chunks/{mode-GRMQCRXR.js → mode-N3QHXCES.js} +2 -2
  209. package/dist-cli/chunks/{optional-demo-registry-W36EWFFB.js → optional-demo-registry-XDH6X24N.js} +2 -2
  210. package/dist-cli/chunks/{perf-QYBAAUZG.js → perf-VOPL26FM.js} +2 -2
  211. package/dist-cli/chunks/{permissions-3QCQ6VF4.js → permissions-CZFKM4UD.js} +2 -2
  212. package/dist-cli/chunks/{record-QPPC2S4E.js → record-FODSOGMA.js} +3 -3
  213. package/dist-cli/chunks/{report-issue-7NMFP4HK.js → report-issue-FBPCZKOV.js} +2 -2
  214. package/dist-cli/chunks/reset-3H35GFO3.js +4 -0
  215. package/dist-cli/chunks/runtime-4OVFRXV5.js +4 -0
  216. package/dist-cli/chunks/{screenshot-command-67AECJFB.js → screenshot-command-7S3FYVO7.js} +7 -7
  217. package/dist-cli/chunks/{screenshot-layers-ASWBYPJL.js → screenshot-layers-CDYHPNC3.js} +3 -3
  218. package/dist-cli/chunks/{screenshots-capture-PXA3HFQK.js → screenshots-capture-EBIIY2YU.js} +2 -2
  219. package/dist-cli/chunks/serve-NELNSCZP.js +44 -0
  220. package/dist-cli/chunks/{setup-7DWPMRSB.js → setup-DF5ZDDIV.js} +2 -2
  221. package/dist-cli/chunks/{skills-S3Y22TUA.js → skills-E2QZF6WZ.js} +2 -2
  222. package/dist-cli/chunks/state-MBZDKZ7U.js +14 -0
  223. package/dist-cli/chunks/{storage-XUIMJWAJ.js → storage-J5BMNVXM.js} +6 -6
  224. package/dist-cli/chunks/store-U3DXC7HH.js +4 -0
  225. package/dist-cli/chunks/telemetry-XIPJVT5E.js +4 -0
  226. package/dist-cli/chunks/{timeline-TMPLQPSP.js → timeline-MC6N5BHY.js} +2 -2
  227. package/dist-cli/chunks/{upgrade-7HDSIM7K.js → upgrade-7UTU53DH.js} +2 -2
  228. package/dist-cli/chunks/upload-2M2KOTYI.js +4 -0
  229. package/dist-cli/chunks/version-VNACNYQK.js +6 -0
  230. package/dist-cli/chunks/{web-DG3WBYD3.js → web-MGJ2TUKR.js} +2 -2
  231. package/dist-cli/chunks/{what-happened-XFVUTZR7.js → what-happened-7DLKH3OD.js} +3 -3
  232. package/dist-lib/agent-daemon-client.cjs +1 -1
  233. package/dist-lib/agent-events.cjs +1 -1
  234. package/dist-lib/agent-identity.cjs +1 -1
  235. package/dist-lib/agent-sessions.cjs +1 -1
  236. package/dist-lib/attached-projects.cjs +1 -1
  237. package/dist-lib/auth/shared-session.cjs +1 -1
  238. package/dist-lib/backend-origin.cjs +1 -1
  239. package/dist-lib/beta.cjs +1 -1
  240. package/dist-lib/beta.mjs +1 -1
  241. package/dist-lib/bridge-constants.cjs +1 -1
  242. package/dist-lib/bridge-contract.cjs +20 -0
  243. package/dist-lib/cli-constants.cjs +1 -1
  244. package/dist-lib/config.cjs +1 -1
  245. package/dist-lib/detox/index.cjs +1 -1
  246. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  247. package/dist-lib/home-paths.cjs +67 -28
  248. package/dist-lib/host/bridge-host.cjs +140 -12
  249. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  250. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  251. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  252. package/dist-lib/host/websocket-proxy.cjs +1 -1
  253. package/dist-lib/index.cjs +2815 -40
  254. package/dist-lib/jump-to-source-babel.cjs +1 -1
  255. package/dist-lib/menu.cjs +1 -1
  256. package/dist-lib/menu.mjs +1 -1
  257. package/dist-lib/metro.cjs +1 -1
  258. package/dist-lib/profiles.cjs +1 -1
  259. package/dist-lib/public-brand.cjs +1 -1
  260. package/dist-lib/render-mode.cjs +1 -1
  261. package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
  262. package/dist-lib/sdk.cjs +2549 -2061
  263. package/dist-lib/sdk.mjs +2543 -2061
  264. package/dist-lib/skills.cjs +480 -280
  265. package/dist-lib/vite.cjs +1 -1
  266. package/package.json +8 -2
  267. package/src/bridge-constants.ts +3 -4
  268. package/src/bridge-contract.ts +251 -0
  269. package/src/connect.ts +83 -0
  270. package/src/disk-cleanup.ts +30 -0
  271. package/src/home-paths.ts +81 -38
  272. package/src/host/bridge-host.ts +134 -6
  273. package/src/index.ts +27 -1
  274. package/src/sdk.ts +8 -0
  275. package/src/sim-client.ts +660 -0
  276. package/dist-cli/chunks/camera-VL73YIKP.js +0 -22
  277. package/dist-cli/chunks/chunk-4NPPOV2N.js +0 -5
  278. package/dist-cli/chunks/chunk-FSUYIVJ6.js +0 -9
  279. package/dist-cli/chunks/chunk-G2WW6L2C.js +0 -23
  280. package/dist-cli/chunks/chunk-KTHV3RUS.js +0 -26
  281. package/dist-cli/chunks/chunk-LF2ZVT7O.js +0 -6
  282. package/dist-cli/chunks/chunk-NFK7T35W.js +0 -4
  283. package/dist-cli/chunks/chunk-TIVZIMMW.js +0 -4
  284. package/dist-cli/chunks/cli-version-WWLPBDQ7.js +0 -4
  285. package/dist-cli/chunks/daemon-G2ME7NLB.js +0 -4
  286. package/dist-cli/chunks/drivers-LDECZGP2.js +0 -4
  287. package/dist-cli/chunks/flow-UEQNVTU7.js +0 -4
  288. package/dist-cli/chunks/help-T5FYSVGB.js +0 -4
  289. package/dist-cli/chunks/home-paths-GT3LFNOR.js +0 -4
  290. package/dist-cli/chunks/inspect-ZA6XF5LD.js +0 -4
  291. package/dist-cli/chunks/install-desktop-TIMUDHPL.js +0 -4
  292. package/dist-cli/chunks/runtime-XOAXMSTU.js +0 -4
  293. package/dist-cli/chunks/serve-BI2NBAXG.js +0 -44
  294. package/dist-cli/chunks/store-JTHEJLAZ.js +0 -4
  295. package/dist-cli/chunks/telemetry-ZYJGD2DB.js +0 -4
  296. package/dist-cli/chunks/upload-GMSZPWM6.js +0 -4
  297. package/dist-cli/chunks/version-HOCHZ37L.js +0 -6
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.313 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.315 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;
@@ -9436,9 +9436,9 @@ function safeChildPlistName(value) {
9436
9436
  name: file.toLowerCase().endsWith(".plist") ? file : `${file}.plist`
9437
9437
  };
9438
9438
  }
9439
- async function readableSettingsBundle(path16) {
9439
+ async function readableSettingsBundle(path17) {
9440
9440
  try {
9441
- const root = await (0, import_promises.stat)((0, import_node_path.join)(path16, "Root.plist"));
9441
+ const root = await (0, import_promises.stat)((0, import_node_path.join)(path17, "Root.plist"));
9442
9442
  return root.isFile() && root.size <= MAX_PLIST_BYTES;
9443
9443
  } catch {
9444
9444
  return false;
@@ -9483,11 +9483,11 @@ async function settingsBundleFingerprint(bundlePath) {
9483
9483
  return null;
9484
9484
  }
9485
9485
  }
9486
- async function parsePlistRecord(path16) {
9486
+ async function parsePlistRecord(path17) {
9487
9487
  try {
9488
- const metadata = await (0, import_promises.stat)(path16);
9488
+ const metadata = await (0, import_promises.stat)(path17);
9489
9489
  if (!metadata.isFile() || metadata.size > MAX_PLIST_BYTES) return null;
9490
- const source = await (0, import_promises.readFile)(path16, "utf8");
9490
+ const source = await (0, import_promises.readFile)(path17, "utf8");
9491
9491
  const parsed = parsePlistSource(source);
9492
9492
  return isRecord2(parsed) ? parsed : null;
9493
9493
  } catch {
@@ -9766,10 +9766,10 @@ function tcpPing(port, timeout = TCP_GATE_MS) {
9766
9766
  sock.connect(port, "localhost");
9767
9767
  });
9768
9768
  }
9769
- function httpGet(port, path16, method = "GET", timeout = TIMEOUT_MS, headers = {}) {
9769
+ function httpGet(port, path17, method = "GET", timeout = TIMEOUT_MS, headers = {}) {
9770
9770
  return new Promise((resolve7) => {
9771
9771
  const req = import_http.default.request(
9772
- { hostname: "localhost", port, path: path16, method, timeout, headers },
9772
+ { hostname: "localhost", port, path: path17, method, timeout, headers },
9773
9773
  (res) => {
9774
9774
  let body = "";
9775
9775
  res.on("data", (c) => body += c.toString());
@@ -10330,11 +10330,12 @@ __export(home_paths_exports, {
10330
10330
  CONFIG_FILE: () => CONFIG_FILE,
10331
10331
  DAEMON_HEARTBEAT_STALE_MS: () => DAEMON_HEARTBEAT_STALE_MS,
10332
10332
  DAEMON_LOCKFILE: () => DAEMON_LOCKFILE,
10333
- DEV_BRIDGE_LOCKFILE: () => DEV_BRIDGE_LOCKFILE,
10333
+ DEV_BRIDGES_DIR: () => DEV_BRIDGES_DIR,
10334
10334
  SOOTSIM_HOME_ENV: () => SOOTSIM_HOME_ENV,
10335
10335
  activeRuntimeDir: () => activeRuntimeDir,
10336
10336
  activeRuntimeFile: () => activeRuntimeFile,
10337
10337
  cacheDir: () => cacheDir,
10338
+ cameraFixturesDir: () => cameraFixturesDir,
10338
10339
  claimDaemonLockfile: () => claimDaemonLockfile,
10339
10340
  configFilePath: () => configFilePath,
10340
10341
  consumeRuntimeUpgradeNotice: () => consumeRuntimeUpgradeNotice,
@@ -10343,6 +10344,7 @@ __export(home_paths_exports, {
10343
10344
  daemonAppLauncherPath: () => daemonAppLauncherPath,
10344
10345
  daemonLockfilePath: () => daemonLockfilePath,
10345
10346
  devBridgeLockfilePath: () => devBridgeLockfilePath,
10347
+ devBridgesDir: () => devBridgesDir,
10346
10348
  electronDir: () => electronDir,
10347
10349
  electronUserDataDir: () => electronUserDataDir,
10348
10350
  electronVersionDir: () => electronVersionDir,
@@ -10354,7 +10356,7 @@ __export(home_paths_exports, {
10354
10356
  profilesDir: () => profilesDir,
10355
10357
  readActiveRuntime: () => readActiveRuntime,
10356
10358
  readDaemonLockfile: () => readDaemonLockfile,
10357
- readDevBridgeLockfile: () => readDevBridgeLockfile,
10359
+ readDevBridgeLockfiles: () => readDevBridgeLockfiles,
10358
10360
  readLiveRuntimeVersions: () => readLiveRuntimeVersions,
10359
10361
  readOrCreateAnonymousId: () => readOrCreateAnonymousId,
10360
10362
  readPrivacyPreferences: () => readPrivacyPreferences,
@@ -10420,6 +10422,9 @@ function electronVersionDir(version) {
10420
10422
  function profilesDir() {
10421
10423
  return import_node_path4.default.join(sootsimHomeDir(), "profiles");
10422
10424
  }
10425
+ function cameraFixturesDir() {
10426
+ return import_node_path4.default.join(sootsimHomeDir(), "camera-fixtures");
10427
+ }
10423
10428
  function daemonAppDir() {
10424
10429
  return import_node_path4.default.join(sootsimHomeDir(), "daemon-app");
10425
10430
  }
@@ -10435,8 +10440,14 @@ function cacheDir() {
10435
10440
  function daemonLockfilePath() {
10436
10441
  return import_node_path4.default.join(sootsimHomeDir(), DAEMON_LOCKFILE);
10437
10442
  }
10438
- function devBridgeLockfilePath() {
10439
- return import_node_path4.default.join(sootsimHomeDir(), DEV_BRIDGE_LOCKFILE);
10443
+ function devBridgesDir() {
10444
+ return import_node_path4.default.join(sootsimHomeDir(), DEV_BRIDGES_DIR);
10445
+ }
10446
+ function devBridgeLockfilePath(bridgePort) {
10447
+ if (!Number.isInteger(bridgePort) || bridgePort <= 0) {
10448
+ throw new Error(`invalid development bridge port: ${bridgePort}`);
10449
+ }
10450
+ return import_node_path4.default.join(devBridgesDir(), `${bridgePort}.json`);
10440
10451
  }
10441
10452
  function configFilePath() {
10442
10453
  return import_node_path4.default.join(sootsimHomeDir(), CONFIG_FILE);
@@ -10565,25 +10576,47 @@ function readDaemonLockfile() {
10565
10576
  return null;
10566
10577
  }
10567
10578
  }
10568
- function readDevBridgeLockfile() {
10579
+ function parseDevBridgeLockfile(raw) {
10569
10580
  try {
10570
- const fd = import_node_fs3.default.openSync(devBridgeLockfilePath(), "r");
10571
- try {
10572
- const buf = Buffer.alloc(DAEMON_LOCKFILE_MAX_BYTES);
10573
- const bytesRead = import_node_fs3.default.readSync(fd, buf, 0, DAEMON_LOCKFILE_MAX_BYTES, 0);
10574
- const raw = buf.subarray(0, bytesRead).toString("utf8");
10575
- const parsed = JSON.parse(raw);
10576
- if (parsed && parsed.schema === 1 && parsed.source === "vite-dev" && typeof parsed.pid === "number" && typeof parsed.bridgePort === "number" && typeof parsed.runtimePort === "number" && typeof parsed.cwd === "string" && typeof parsed.startedAt === "number" && typeof parsed.heartbeatAt === "number" && (parsed.shellPort === void 0 || typeof parsed.shellPort === "number") && (parsed.servedRuntimes === void 0 || Array.isArray(parsed.servedRuntimes) && parsed.servedRuntimes.every((version) => typeof version === "string"))) {
10577
- return parsed;
10578
- }
10579
- return null;
10580
- } finally {
10581
- import_node_fs3.default.closeSync(fd);
10581
+ const parsed = JSON.parse(raw);
10582
+ if (parsed && parsed.schema === 1 && parsed.source === "vite-dev" && typeof parsed.pid === "number" && typeof parsed.bridgePort === "number" && typeof parsed.runtimePort === "number" && typeof parsed.cwd === "string" && typeof parsed.startedAt === "number" && typeof parsed.heartbeatAt === "number" && (parsed.shellPort === void 0 || typeof parsed.shellPort === "number") && (parsed.servedRuntimes === void 0 || Array.isArray(parsed.servedRuntimes) && parsed.servedRuntimes.every((version) => typeof version === "string"))) {
10583
+ return parsed;
10582
10584
  }
10585
+ return null;
10583
10586
  } catch {
10584
10587
  return null;
10585
10588
  }
10586
10589
  }
10590
+ function readDevBridgeLockfiles() {
10591
+ let entries;
10592
+ try {
10593
+ entries = import_node_fs3.default.readdirSync(devBridgesDir(), { withFileTypes: true });
10594
+ } catch {
10595
+ return [];
10596
+ }
10597
+ const lockfiles = [];
10598
+ for (const entry of entries) {
10599
+ if (!entry.isFile() || !/^\d+\.json$/.test(entry.name)) continue;
10600
+ try {
10601
+ const filePath = import_node_path4.default.join(devBridgesDir(), entry.name);
10602
+ const fd = import_node_fs3.default.openSync(filePath, "r");
10603
+ try {
10604
+ const buf = Buffer.alloc(DAEMON_LOCKFILE_MAX_BYTES);
10605
+ const bytesRead = import_node_fs3.default.readSync(fd, buf, 0, DAEMON_LOCKFILE_MAX_BYTES, 0);
10606
+ const lockfile = parseDevBridgeLockfile(
10607
+ buf.subarray(0, bytesRead).toString("utf8")
10608
+ );
10609
+ if (lockfile && entry.name === `${lockfile.bridgePort}.json`) {
10610
+ lockfiles.push(lockfile);
10611
+ }
10612
+ } finally {
10613
+ import_node_fs3.default.closeSync(fd);
10614
+ }
10615
+ } catch {
10616
+ }
10617
+ }
10618
+ return lockfiles.sort((left, right) => left.bridgePort - right.bridgePort);
10619
+ }
10587
10620
  function isDaemonLockfileFresh(lock, now = Date.now()) {
10588
10621
  if (!lock) return false;
10589
10622
  if (now - lock.heartbeatAt > DAEMON_HEARTBEAT_STALE_MS) return false;
@@ -10611,8 +10644,8 @@ function readLiveRuntimeVersions() {
10611
10644
  if (daemon.activeRuntime) versions.add(daemon.activeRuntime);
10612
10645
  for (const version of daemon.servedRuntimes ?? []) versions.add(version);
10613
10646
  }
10614
- const devBridge = readDevBridgeLockfile();
10615
- if (isDevBridgeLockfileFresh(devBridge)) {
10647
+ for (const devBridge of readDevBridgeLockfiles()) {
10648
+ if (!isDevBridgeLockfileFresh(devBridge)) continue;
10616
10649
  for (const version of devBridge.servedRuntimes ?? []) versions.add(version);
10617
10650
  }
10618
10651
  return [...versions].sort();
@@ -10625,11 +10658,12 @@ function writeDaemonLockfile(data) {
10625
10658
  import_node_fs3.default.renameSync(tmp, daemonLockfilePath());
10626
10659
  }
10627
10660
  function writeDevBridgeLockfile(data) {
10628
- ensureSootsimHome();
10629
- const tmp = `${devBridgeLockfilePath()}.tmp`;
10661
+ import_node_fs3.default.mkdirSync(devBridgesDir(), { recursive: true });
10662
+ const lockfilePath = devBridgeLockfilePath(data.bridgePort);
10663
+ const tmp = `${lockfilePath}.${data.pid}.tmp`;
10630
10664
  import_node_fs3.default.writeFileSync(tmp, `${JSON.stringify(data, null, 2)}
10631
10665
  `, "utf8");
10632
- import_node_fs3.default.renameSync(tmp, devBridgeLockfilePath());
10666
+ import_node_fs3.default.renameSync(tmp, lockfilePath);
10633
10667
  }
10634
10668
  function claimDaemonLockfile(data) {
10635
10669
  ensureSootsimHome();
@@ -10646,13 +10680,16 @@ function removeDaemonLockfile() {
10646
10680
  } catch {
10647
10681
  }
10648
10682
  }
10649
- function removeDevBridgeLockfile() {
10683
+ function removeDevBridgeLockfile(owner) {
10650
10684
  try {
10651
- import_node_fs3.default.unlinkSync(devBridgeLockfilePath());
10685
+ const lockfilePath = devBridgeLockfilePath(owner.bridgePort);
10686
+ const lockfile = parseDevBridgeLockfile(import_node_fs3.default.readFileSync(lockfilePath, "utf8"));
10687
+ if (lockfile?.pid !== owner.pid || lockfile.startedAt !== owner.startedAt) return;
10688
+ import_node_fs3.default.unlinkSync(lockfilePath);
10652
10689
  } catch {
10653
10690
  }
10654
10691
  }
10655
- var import_node_crypto, import_node_fs3, import_node_os, import_node_path4, SOOTSIM_HOME_ENV, ACTIVE_RUNTIME_FILE, DAEMON_LOCKFILE, DEV_BRIDGE_LOCKFILE, CONFIG_FILE, DAEMON_HEARTBEAT_STALE_MS, DAEMON_LOCKFILE_MAX_BYTES;
10692
+ var import_node_crypto, import_node_fs3, import_node_os, import_node_path4, SOOTSIM_HOME_ENV, ACTIVE_RUNTIME_FILE, DAEMON_LOCKFILE, DEV_BRIDGES_DIR, CONFIG_FILE, DAEMON_HEARTBEAT_STALE_MS, DAEMON_LOCKFILE_MAX_BYTES;
10656
10693
  var init_home_paths = __esm({
10657
10694
  "src/home-paths.ts"() {
10658
10695
  "use strict";
@@ -10663,7 +10700,7 @@ var init_home_paths = __esm({
10663
10700
  SOOTSIM_HOME_ENV = "SOOTSIM_HOME";
10664
10701
  ACTIVE_RUNTIME_FILE = "active";
10665
10702
  DAEMON_LOCKFILE = "daemon.json";
10666
- DEV_BRIDGE_LOCKFILE = "dev-bridge.json";
10703
+ DEV_BRIDGES_DIR = "dev-bridges";
10667
10704
  CONFIG_FILE = "config.json";
10668
10705
  DAEMON_HEARTBEAT_STALE_MS = 3e4;
10669
10706
  DAEMON_LOCKFILE_MAX_BYTES = 16 * 1024;
@@ -13211,7 +13248,7 @@ function unrefTimer(timer) {
13211
13248
  timer.unref();
13212
13249
  }
13213
13250
  }
13214
- var import_child_process3, import_fs2, import_http3, import_path2, import_ws2, SOOTSIM_CROSS_ORIGIN_EMBEDDER_POLICY, WRITE_COMMAND_TYPES, FORCE_CLOSE_GRACE_MS, FORCE_CLOSE_TERMINATE_MS, DAEMON_HEARTBEAT_INTERVAL_MS, DEFAULT_RUNTIME_UPDATE_INTERVAL_MS, RUNTIME_UPDATE_INTERVAL_ENV, HTTP_MIME_TYPES, SootSimBridgeHost;
13251
+ var import_child_process3, import_fs2, import_http3, import_path2, import_ws2, SOOTSIM_CROSS_ORIGIN_EMBEDDER_POLICY, WRITE_COMMAND_TYPES, FORCE_CLOSE_GRACE_MS, FORCE_CLOSE_TERMINATE_MS, DAEMON_HEARTBEAT_INTERVAL_MS, DEFAULT_RUNTIME_UPDATE_INTERVAL_MS, RUNTIME_UPDATE_INTERVAL_ENV, HTTP_MIME_TYPES, CAMERA_FIXTURE_ROUTE, CAMERA_FIXTURE_NAME_RE, SootSimBridgeHost;
13215
13252
  var init_bridge_host = __esm({
13216
13253
  "src/host/bridge-host.ts"() {
13217
13254
  "use strict";
@@ -13231,7 +13268,14 @@ var init_bridge_host = __esm({
13231
13268
  init_open_url();
13232
13269
  init_websocket_proxy();
13233
13270
  SOOTSIM_CROSS_ORIGIN_EMBEDDER_POLICY = "require-corp";
13234
- WRITE_COMMAND_TYPES = /* @__PURE__ */ new Set(["tap", "keyboard"]);
13271
+ WRITE_COMMAND_TYPES = /* @__PURE__ */ new Set([
13272
+ "tap",
13273
+ "keyboard",
13274
+ "longPress",
13275
+ "perform",
13276
+ "reset",
13277
+ "camera"
13278
+ ]);
13235
13279
  FORCE_CLOSE_GRACE_MS = 2e3;
13236
13280
  FORCE_CLOSE_TERMINATE_MS = 1e3;
13237
13281
  DAEMON_HEARTBEAT_INTERVAL_MS = 5e3;
@@ -13258,8 +13302,14 @@ var init_bridge_host = __esm({
13258
13302
  ".woff": "font/woff",
13259
13303
  ".woff2": "font/woff2",
13260
13304
  ".map": "application/json",
13261
- ".txt": "text/plain; charset=utf-8"
13305
+ ".txt": "text/plain; charset=utf-8",
13306
+ ".mp4": "video/mp4",
13307
+ ".m4v": "video/mp4",
13308
+ ".webm": "video/webm",
13309
+ ".mov": "video/quicktime"
13262
13310
  };
13311
+ CAMERA_FIXTURE_ROUTE = "/__camera-fixtures/";
13312
+ CAMERA_FIXTURE_NAME_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
13263
13313
  SootSimBridgeHost = class _SootSimBridgeHost {
13264
13314
  port;
13265
13315
  openUrlHandler;
@@ -14019,6 +14069,89 @@ var init_bridge_host = __esm({
14019
14069
  });
14020
14070
  return this.inflightScan;
14021
14071
  }
14072
+ // serves one staged camera fixture. addressed by basename inside
14073
+ // ~/.sootsim/camera-fixtures, never by a caller-supplied path, so there is
14074
+ // no traversal surface to defend: a name that is not a plain basename is
14075
+ // rejected outright. range support is not optional here — chrome asks for
14076
+ // byte ranges on media and cannot seek or loop a fixture without it.
14077
+ handleCameraFixture(name2, req, res) {
14078
+ const method = (req.method || "GET").toUpperCase();
14079
+ const cors = {
14080
+ "Access-Control-Allow-Origin": "*",
14081
+ "Cache-Control": "no-store"
14082
+ };
14083
+ if (method === "OPTIONS") {
14084
+ res.writeHead(204, {
14085
+ ...cors,
14086
+ "Access-Control-Allow-Methods": "GET, HEAD, OPTIONS",
14087
+ "Access-Control-Allow-Headers": "range"
14088
+ });
14089
+ res.end();
14090
+ return;
14091
+ }
14092
+ if (method !== "GET" && method !== "HEAD") {
14093
+ res.writeHead(405, cors);
14094
+ res.end();
14095
+ return;
14096
+ }
14097
+ if (!CAMERA_FIXTURE_NAME_RE.test(name2)) {
14098
+ res.writeHead(400, { ...cors, "Content-Type": "text/plain; charset=utf-8" });
14099
+ res.end("bad fixture name");
14100
+ return;
14101
+ }
14102
+ const root = cameraFixturesDir();
14103
+ const filePath = import_path2.default.join(root, name2);
14104
+ let stats;
14105
+ try {
14106
+ stats = import_fs2.default.lstatSync(filePath);
14107
+ } catch {
14108
+ res.writeHead(404, { ...cors, "Content-Type": "text/plain; charset=utf-8" });
14109
+ res.end("not found");
14110
+ return;
14111
+ }
14112
+ if (!stats.isFile()) {
14113
+ res.writeHead(403, { ...cors, "Content-Type": "text/plain; charset=utf-8" });
14114
+ res.end("forbidden");
14115
+ return;
14116
+ }
14117
+ const contentType = HTTP_MIME_TYPES[import_path2.default.extname(filePath).toLowerCase()] || "application/octet-stream";
14118
+ const total = stats.size;
14119
+ const range = /^bytes=(\d*)-(\d*)$/.exec(req.headers.range || "");
14120
+ if (range) {
14121
+ const [, startRaw, endRaw] = range;
14122
+ const start = startRaw ? Number(startRaw) : 0;
14123
+ const end = endRaw ? Math.min(Number(endRaw), total - 1) : total - 1;
14124
+ if (!Number.isFinite(start) || start > end || start >= total) {
14125
+ res.writeHead(416, { ...cors, "Content-Range": `bytes */${total}` });
14126
+ res.end();
14127
+ return;
14128
+ }
14129
+ res.writeHead(206, {
14130
+ ...cors,
14131
+ "Content-Type": contentType,
14132
+ "Content-Range": `bytes ${start}-${end}/${total}`,
14133
+ "Accept-Ranges": "bytes",
14134
+ "Content-Length": String(end - start + 1)
14135
+ });
14136
+ if (method === "HEAD") {
14137
+ res.end();
14138
+ return;
14139
+ }
14140
+ import_fs2.default.createReadStream(filePath, { start, end }).pipe(res);
14141
+ return;
14142
+ }
14143
+ res.writeHead(200, {
14144
+ ...cors,
14145
+ "Content-Type": contentType,
14146
+ "Accept-Ranges": "bytes",
14147
+ "Content-Length": String(total)
14148
+ });
14149
+ if (method === "HEAD") {
14150
+ res.end();
14151
+ return;
14152
+ }
14153
+ import_fs2.default.createReadStream(filePath).pipe(res);
14154
+ }
14022
14155
  handleServerScan(res) {
14023
14156
  const sendJson = (body) => {
14024
14157
  res.writeHead(200, {
@@ -14242,6 +14375,10 @@ var init_bridge_host = __esm({
14242
14375
  res.end("method not allowed");
14243
14376
  return;
14244
14377
  }
14378
+ if (url.pathname.startsWith(CAMERA_FIXTURE_ROUTE)) {
14379
+ this.handleCameraFixture(url.pathname.slice(CAMERA_FIXTURE_ROUTE.length), req, res);
14380
+ return;
14381
+ }
14245
14382
  if (method !== "GET" && method !== "HEAD") {
14246
14383
  res.writeHead(405, { Allow: "GET, HEAD" });
14247
14384
  res.end("method not allowed");
@@ -14710,7 +14847,11 @@ var init_bridge_host = __esm({
14710
14847
  }
14711
14848
  if (this.shouldWriteDevLockfile) {
14712
14849
  try {
14713
- removeDevBridgeLockfile();
14850
+ removeDevBridgeLockfile({
14851
+ bridgePort: this.effectivePort,
14852
+ pid: process.pid,
14853
+ startedAt: this.startedAt
14854
+ });
14714
14855
  } catch {
14715
14856
  }
14716
14857
  }
@@ -15465,6 +15606,53 @@ __export(ws_bridge_exports, {
15465
15606
  resolveDefaultBridgePort: () => resolveDefaultBridgePort,
15466
15607
  worldForBridgePort: () => worldForBridgePort
15467
15608
  });
15609
+ function liveDevBridgeLockfiles() {
15610
+ return readDevBridgeLockfiles().filter((lockfile) => isDevBridgeLockfileFresh(lockfile));
15611
+ }
15612
+ function checkoutRoot(cwd) {
15613
+ let current = import_node_path10.default.resolve(cwd);
15614
+ while (true) {
15615
+ if (import_node_fs9.default.existsSync(import_node_path10.default.join(current, ".git"))) return current;
15616
+ const parent = import_node_path10.default.dirname(current);
15617
+ if (parent === current) return null;
15618
+ current = parent;
15619
+ }
15620
+ }
15621
+ function pathContains(parent, child) {
15622
+ const relative = import_node_path10.default.relative(parent, child);
15623
+ return relative === "" || relative !== ".." && !relative.startsWith(`..${import_node_path10.default.sep}`) && !import_node_path10.default.isAbsolute(relative);
15624
+ }
15625
+ function resolveDefaultDevBridgeLockfile() {
15626
+ const lockfiles = liveDevBridgeLockfiles();
15627
+ if (lockfiles.length === 0) return null;
15628
+ if (process.env.SOOTSIM_BRIDGE_PORT !== void 0 || process.env.PORT_OFFSET !== void 0) {
15629
+ const requestedPort = resolveSootsimBridgePort({
15630
+ explicitPort: process.env.SOOTSIM_BRIDGE_PORT,
15631
+ portOffset: process.env.PORT_OFFSET
15632
+ });
15633
+ const requested = lockfiles.find((lockfile) => lockfile.bridgePort === requestedPort);
15634
+ if (requested) return requested;
15635
+ }
15636
+ let cwd = null;
15637
+ try {
15638
+ cwd = process.cwd();
15639
+ } catch {
15640
+ }
15641
+ if (cwd) {
15642
+ const currentCheckout = checkoutRoot(cwd);
15643
+ const sameCheckout = lockfiles.filter((lockfile) => {
15644
+ const bridgeCheckout = checkoutRoot(lockfile.cwd);
15645
+ if (currentCheckout && bridgeCheckout) return currentCheckout === bridgeCheckout;
15646
+ return pathContains(cwd, lockfile.cwd) || pathContains(lockfile.cwd, cwd);
15647
+ });
15648
+ if (sameCheckout.length > 0) {
15649
+ return sameCheckout.find(
15650
+ (lockfile) => lockfile.bridgePort === DEFAULT_SOOTSIM_BRIDGE_PORT
15651
+ ) ?? sameCheckout[0];
15652
+ }
15653
+ }
15654
+ return lockfiles.find((lockfile) => lockfile.bridgePort === DEFAULT_SOOTSIM_BRIDGE_PORT) ?? lockfiles[0];
15655
+ }
15468
15656
  function resolveBridgeWorld() {
15469
15657
  const homeDir = sootsimHomeDir();
15470
15658
  if (resolvedWorld?.homeDir === homeDir) return resolvedWorld.world;
@@ -15473,8 +15661,8 @@ function resolveBridgeWorld() {
15473
15661
  return world;
15474
15662
  }
15475
15663
  function resolveBridgeWorldFresh() {
15476
- const dev = readDevBridgeLockfile();
15477
- if (dev && isDevBridgeLockfileFresh(dev)) {
15664
+ const dev = resolveDefaultDevBridgeLockfile();
15665
+ if (dev) {
15478
15666
  return {
15479
15667
  source: "dev-bridge",
15480
15668
  bridgePort: dev.bridgePort,
@@ -15498,8 +15686,8 @@ function resolveBridgeWorldFresh() {
15498
15686
  };
15499
15687
  }
15500
15688
  function worldForBridgePort(port) {
15501
- const dev = readDevBridgeLockfile();
15502
- if (dev && isDevBridgeLockfileFresh(dev) && dev.bridgePort === port) {
15689
+ const dev = liveDevBridgeLockfiles().find((lockfile) => lockfile.bridgePort === port);
15690
+ if (dev) {
15503
15691
  return {
15504
15692
  source: "dev-bridge",
15505
15693
  shellBaseUrl: dev.shellPort ? `http://localhost:${dev.shellPort}/` : DEFAULT_SOOTSIM_SHELL_URL
@@ -15523,8 +15711,10 @@ function bridgePortForShellBaseUrl(baseUrl) {
15523
15711
  return null;
15524
15712
  }
15525
15713
  if (!Number.isFinite(port) || port <= 0) return null;
15526
- const dev = readDevBridgeLockfile();
15527
- if (dev && isDevBridgeLockfileFresh(dev) && (dev.shellPort === port || dev.runtimePort === port || dev.bridgePort === port)) {
15714
+ const dev = liveDevBridgeLockfiles().find(
15715
+ (lockfile) => lockfile.shellPort === port || lockfile.runtimePort === port || lockfile.bridgePort === port
15716
+ );
15717
+ if (dev) {
15528
15718
  return dev.bridgePort;
15529
15719
  }
15530
15720
  const lock = readDaemonLockfile();
@@ -15539,7 +15729,7 @@ function resolveDefaultBridgePort() {
15539
15729
  function printBridgeWorldNotice(bridgePort, shellBaseUrl) {
15540
15730
  const world = worldForBridgePort(bridgePort);
15541
15731
  const kind = world?.source === "dev-bridge" ? "dev bridge" : world?.source === "daemon" ? "daemon bridge" : "bridge";
15542
- const origin = world ? `${world.source}.json` : "no lockfile";
15732
+ const origin = world?.source === "dev-bridge" ? `dev-bridges/${bridgePort}.json` : world?.source === "daemon" ? "daemon.json" : "no lockfile";
15543
15733
  const shell = shellBaseUrl ?? world?.shellBaseUrl;
15544
15734
  process.stderr.write(
15545
15735
  ` \u2192 ${kind} :${bridgePort} (${shell ? `shell ${shell}, ` : ""}${origin})
@@ -15659,6 +15849,8 @@ function createBridge(wsPort, opts = {}) {
15659
15849
  reject(new Error(`could not connect to ws://localhost:${wsPort}${cause}`));
15660
15850
  });
15661
15851
  });
15852
+ ready.catch(() => {
15853
+ });
15662
15854
  let warnedAboutContention = false;
15663
15855
  let noticedTarget = false;
15664
15856
  function maybeNoticeTarget(cmdType, simId) {
@@ -15851,16 +16043,18 @@ async function evalInBridge(bridge, code, opts = {}) {
15851
16043
  if (opts.acquireLock) payload.acquireLock = true;
15852
16044
  return bridge.send(payload);
15853
16045
  }
15854
- async function callInBridge(bridge, path16, ...args) {
15855
- return bridge.send({ type: "call", path: path16, args });
16046
+ async function callInBridge(bridge, path17, ...args) {
16047
+ return bridge.send({ type: "call", path: path17, args });
15856
16048
  }
15857
- async function callInBridgeWrite(bridge, path16, ...args) {
15858
- return bridge.send({ type: "call", path: path16, args, acquireLock: true });
16049
+ async function callInBridgeWrite(bridge, path17, ...args) {
16050
+ return bridge.send({ type: "call", path: path17, args, acquireLock: true });
15859
16051
  }
15860
- var import_ws3, resolvedWorld, BridgeSimLockedError;
16052
+ var import_node_fs9, import_node_path10, import_ws3, resolvedWorld, BridgeSimLockedError;
15861
16053
  var init_ws_bridge = __esm({
15862
16054
  "cli/ws-bridge.ts"() {
15863
16055
  "use strict";
16056
+ import_node_fs9 = __toESM(require("node:fs"), 1);
16057
+ import_node_path10 = __toESM(require("node:path"), 1);
15864
16058
  import_ws3 = require("ws");
15865
16059
  init_bridge_constants();
15866
16060
  init_cli_constants();
@@ -15915,8 +16109,8 @@ function getDefaultPortForProtocol(protocol) {
15915
16109
  return protocol === "https:" ? 443 : 80;
15916
16110
  }
15917
16111
  function isBaseServerUrl(url) {
15918
- const path16 = url.pathname || "/";
15919
- return (path16 === "/" || path16 === "") && !url.search && !url.hash;
16112
+ const path17 = url.pathname || "/";
16113
+ return (path17 === "/" || path17 === "") && !url.search && !url.hash;
15920
16114
  }
15921
16115
  async function resolvePortViaServerScan(port) {
15922
16116
  try {
@@ -16136,9 +16330,9 @@ var init_profiles = __esm({
16136
16330
  // cli/app-project.ts
16137
16331
  function firstNamedFile(dir, names) {
16138
16332
  for (const name2 of names) {
16139
- const candidate = import_node_path10.default.join(dir, name2);
16333
+ const candidate = import_node_path11.default.join(dir, name2);
16140
16334
  try {
16141
- if (import_node_fs9.default.statSync(candidate).isFile()) return candidate;
16335
+ if (import_node_fs10.default.statSync(candidate).isFile()) return candidate;
16142
16336
  } catch {
16143
16337
  }
16144
16338
  }
@@ -16152,7 +16346,7 @@ function findSootSimAppConfig(dir) {
16152
16346
  }
16153
16347
  function readWorkspacePatterns(rootDir) {
16154
16348
  try {
16155
- const value = JSON.parse(import_node_fs9.default.readFileSync(import_node_path10.default.join(rootDir, "package.json"), "utf8"));
16349
+ const value = JSON.parse(import_node_fs10.default.readFileSync(import_node_path11.default.join(rootDir, "package.json"), "utf8"));
16156
16350
  if (!value || typeof value !== "object") return [];
16157
16351
  const workspaces = Reflect.get(value, "workspaces");
16158
16352
  if (Array.isArray(workspaces)) {
@@ -16174,7 +16368,7 @@ function workspaceDirectories(rootDir) {
16174
16368
  complete = false;
16175
16369
  break;
16176
16370
  }
16177
- if (!pattern || import_node_path10.default.isAbsolute(pattern)) continue;
16371
+ if (!pattern || import_node_path11.default.isAbsolute(pattern)) continue;
16178
16372
  const segments = pattern.split(/[\\/]+/).filter(Boolean);
16179
16373
  if (segments.length === 0 || segments.length > MAX_SCAN_DEPTH) continue;
16180
16374
  if (segments.some((segment) => segment === ".." || segment === "**")) continue;
@@ -16190,19 +16384,19 @@ function workspaceDirectories(rootDir) {
16190
16384
  if (segment === "*") {
16191
16385
  let entries;
16192
16386
  try {
16193
- entries = import_node_fs9.default.readdirSync(parent, { withFileTypes: true });
16387
+ entries = import_node_fs10.default.readdirSync(parent, { withFileTypes: true });
16194
16388
  } catch {
16195
16389
  continue;
16196
16390
  }
16197
16391
  for (const entry of entries) {
16198
16392
  if (!entry.isDirectory() || entry.isSymbolicLink()) continue;
16199
16393
  if (entry.name.startsWith(".") || SKIP_DIR_NAMES.has(entry.name)) continue;
16200
- next.push(import_node_path10.default.join(parent, entry.name));
16394
+ next.push(import_node_path11.default.join(parent, entry.name));
16201
16395
  }
16202
16396
  } else if (!segment.includes("*")) {
16203
- const candidate = import_node_path10.default.join(parent, segment);
16397
+ const candidate = import_node_path11.default.join(parent, segment);
16204
16398
  try {
16205
- const stat2 = import_node_fs9.default.lstatSync(candidate);
16399
+ const stat2 = import_node_fs10.default.lstatSync(candidate);
16206
16400
  if (stat2.isDirectory() && !stat2.isSymbolicLink()) next.push(candidate);
16207
16401
  } catch {
16208
16402
  }
@@ -16235,7 +16429,7 @@ function boundedConfigDirectories(rootDir) {
16235
16429
  if (current.depth >= MAX_SCAN_DEPTH) continue;
16236
16430
  let entries;
16237
16431
  try {
16238
- entries = import_node_fs9.default.readdirSync(current.dir, { withFileTypes: true });
16432
+ entries = import_node_fs10.default.readdirSync(current.dir, { withFileTypes: true });
16239
16433
  } catch {
16240
16434
  continue;
16241
16435
  }
@@ -16246,22 +16440,22 @@ function boundedConfigDirectories(rootDir) {
16246
16440
  complete = false;
16247
16441
  break;
16248
16442
  }
16249
- queue.push({ dir: import_node_path10.default.join(current.dir, entry.name), depth: current.depth + 1 });
16443
+ queue.push({ dir: import_node_path11.default.join(current.dir, entry.name), depth: current.depth + 1 });
16250
16444
  }
16251
16445
  }
16252
16446
  return { directories: matches, complete: complete && nextIndex === queue.length };
16253
16447
  }
16254
16448
  function resolveAppProject(startDir, opts = {}) {
16255
- const rootDir = import_node_path10.default.resolve(startDir);
16449
+ const rootDir = import_node_path11.default.resolve(startDir);
16256
16450
  const rootConfigPath = findSootSimAppConfig(rootDir);
16257
- if (findExpoAppConfig(rootDir) || opts.trustStart && import_node_fs9.default.existsSync(rootDir)) {
16451
+ if (findExpoAppConfig(rootDir) || opts.trustStart && import_node_fs10.default.existsSync(rootDir)) {
16258
16452
  let configPath = rootConfigPath;
16259
16453
  if (!configPath && opts.trustStart) {
16260
- let parent = import_node_path10.default.dirname(rootDir);
16261
- for (let depth = 0; depth < MAX_SCAN_DEPTH && parent !== import_node_path10.default.dirname(parent); depth++) {
16454
+ let parent = import_node_path11.default.dirname(rootDir);
16455
+ for (let depth = 0; depth < MAX_SCAN_DEPTH && parent !== import_node_path11.default.dirname(parent); depth++) {
16262
16456
  configPath = findSootSimAppConfig(parent);
16263
16457
  if (configPath) break;
16264
- parent = import_node_path10.default.dirname(parent);
16458
+ parent = import_node_path11.default.dirname(parent);
16265
16459
  }
16266
16460
  }
16267
16461
  return { appDir: rootDir, configPath };
@@ -16293,12 +16487,12 @@ function resolveAppProject(startDir, opts = {}) {
16293
16487
  if (rootConfigPath) return { appDir: rootDir, configPath: rootConfigPath };
16294
16488
  return null;
16295
16489
  }
16296
- var import_node_fs9, import_node_path10, EXPO_APP_CONFIG_NAMES, SOOTSIM_APP_CONFIG_NAMES, SKIP_DIR_NAMES, MAX_SCANNED_DIRECTORIES, MAX_SCAN_DEPTH;
16490
+ var import_node_fs10, import_node_path11, EXPO_APP_CONFIG_NAMES, SOOTSIM_APP_CONFIG_NAMES, SKIP_DIR_NAMES, MAX_SCANNED_DIRECTORIES, MAX_SCAN_DEPTH;
16297
16491
  var init_app_project = __esm({
16298
16492
  "cli/app-project.ts"() {
16299
16493
  "use strict";
16300
- import_node_fs9 = __toESM(require("node:fs"), 1);
16301
- import_node_path10 = __toESM(require("node:path"), 1);
16494
+ import_node_fs10 = __toESM(require("node:fs"), 1);
16495
+ import_node_path11 = __toESM(require("node:path"), 1);
16302
16496
  EXPO_APP_CONFIG_NAMES = [
16303
16497
  "app.config.js",
16304
16498
  "app.config.ts",
@@ -16399,7 +16593,7 @@ function localOriginOf(value) {
16399
16593
  }
16400
16594
  function hasPackageJson(dir) {
16401
16595
  try {
16402
- return import_node_fs10.default.statSync(import_node_path11.default.join(dir, "package.json")).isFile();
16596
+ return import_node_fs11.default.statSync(import_node_path12.default.join(dir, "package.json")).isFile();
16403
16597
  } catch {
16404
16598
  return false;
16405
16599
  }
@@ -16408,7 +16602,7 @@ function discoverNativeLinkedAppFonts(opts) {
16408
16602
  const metroOrigin = localOriginOf(opts.bundleUrl);
16409
16603
  if (!metroOrigin) return [];
16410
16604
  const explicit = opts.repoDir || process.env.CONTRAST_REPO_DIR;
16411
- const startDir = explicit ? import_node_path11.default.resolve(explicit) : process.cwd();
16605
+ const startDir = explicit ? import_node_path12.default.resolve(explicit) : process.cwd();
16412
16606
  if (!explicit && !hasPackageJson(startDir)) return [];
16413
16607
  const project = resolveAppProject(startDir, { trustStart: opts.exactProjectDir });
16414
16608
  const selectedAppDir = project?.appDir ?? null;
@@ -16454,13 +16648,13 @@ function discoverNativeLinkedAppFonts(opts) {
16454
16648
  scannedFontDirectories++;
16455
16649
  let entries;
16456
16650
  try {
16457
- entries = import_node_fs10.default.readdirSync(current.dir, { withFileTypes: true });
16651
+ entries = import_node_fs11.default.readdirSync(current.dir, { withFileTypes: true });
16458
16652
  } catch {
16459
16653
  continue;
16460
16654
  }
16461
16655
  for (const entry of entries) {
16462
16656
  if (entry.isSymbolicLink()) continue;
16463
- const fullPath = import_node_path11.default.join(current.dir, entry.name);
16657
+ const fullPath = import_node_path12.default.join(current.dir, entry.name);
16464
16658
  if (entry.isDirectory()) {
16465
16659
  if (current.depth < 4 && pending.length < 64 && !entry.name.startsWith(".")) {
16466
16660
  pending.push({ dir: fullPath, depth: current.depth + 1 });
@@ -16468,15 +16662,15 @@ function discoverNativeLinkedAppFonts(opts) {
16468
16662
  continue;
16469
16663
  }
16470
16664
  if (!entry.isFile() || !/\.(ttf|otf|woff|woff2)$/i.test(entry.name)) continue;
16471
- const relative = import_node_path11.default.relative(root, fullPath).split(import_node_path11.default.sep).join("/");
16472
- fontPaths.push(import_node_path11.default.posix.join(metroPathPrefix, relative));
16665
+ const relative = import_node_path12.default.relative(root, fullPath).split(import_node_path12.default.sep).join("/");
16666
+ fontPaths.push(import_node_path12.default.posix.join(metroPathPrefix, relative));
16473
16667
  if (fontPaths.length >= 256) break;
16474
16668
  }
16475
16669
  }
16476
16670
  };
16477
16671
  try {
16478
16672
  const packageJson = JSON.parse(
16479
- import_node_fs10.default.readFileSync(import_node_path11.default.join(selectedAppDir, "package.json"), "utf8")
16673
+ import_node_fs11.default.readFileSync(import_node_path12.default.join(selectedAppDir, "package.json"), "utf8")
16480
16674
  );
16481
16675
  const dependencyNames = /* @__PURE__ */ new Set();
16482
16676
  for (const field of ["dependencies", "devDependencies", "optionalDependencies"]) {
@@ -16491,24 +16685,24 @@ function discoverNativeLinkedAppFonts(opts) {
16491
16685
  const config = Reflect.get(packageJson, "reactNativeVectorIcons");
16492
16686
  const configuredFontDir = config && typeof config === "object" ? Reflect.get(config, "fontDir") : null;
16493
16687
  const configuredDir = typeof configuredFontDir === "string" ? configuredFontDir : "rnvi-fonts";
16494
- const root = import_node_path11.default.resolve(selectedAppDir, configuredDir);
16495
- const relativeRoot = import_node_path11.default.relative(selectedAppDir, root);
16496
- if (!relativeRoot.startsWith("..") && !import_node_path11.default.isAbsolute(relativeRoot)) {
16497
- collectFontFiles(root, relativeRoot.split(import_node_path11.default.sep).join("/"));
16688
+ const root = import_node_path12.default.resolve(selectedAppDir, configuredDir);
16689
+ const relativeRoot = import_node_path12.default.relative(selectedAppDir, root);
16690
+ if (!relativeRoot.startsWith("..") && !import_node_path12.default.isAbsolute(relativeRoot)) {
16691
+ collectFontFiles(root, relativeRoot.split(import_node_path12.default.sep).join("/"));
16498
16692
  }
16499
- const vectorIconRequire = (0, import_node_module2.createRequire)(import_node_path11.default.join(selectedAppDir, "package.json"));
16693
+ const vectorIconRequire = (0, import_node_module2.createRequire)(import_node_path12.default.join(selectedAppDir, "package.json"));
16500
16694
  for (const dependencyName of vectorIconDependencies) {
16501
16695
  try {
16502
16696
  const entryPath = vectorIconRequire.resolve(dependencyName);
16503
- const dependencyPath = dependencyName.split("/").join(import_node_path11.default.sep);
16504
- const marker = `${import_node_path11.default.sep}node_modules${import_node_path11.default.sep}${dependencyPath}${import_node_path11.default.sep}`;
16697
+ const dependencyPath = dependencyName.split("/").join(import_node_path12.default.sep);
16698
+ const marker = `${import_node_path12.default.sep}node_modules${import_node_path12.default.sep}${dependencyPath}${import_node_path12.default.sep}`;
16505
16699
  const markerIndex = entryPath.lastIndexOf(marker);
16506
16700
  if (markerIndex === -1) continue;
16507
16701
  const packageRoot = entryPath.slice(0, markerIndex + marker.length - 1);
16508
16702
  const fontsDir = dependencyName === "react-native-vector-icons" ? "Fonts" : "fonts";
16509
16703
  collectFontFiles(
16510
- import_node_path11.default.join(packageRoot, fontsDir),
16511
- import_node_path11.default.posix.join("node_modules", dependencyName, fontsDir)
16704
+ import_node_path12.default.join(packageRoot, fontsDir),
16705
+ import_node_path12.default.posix.join("node_modules", dependencyName, fontsDir)
16512
16706
  );
16513
16707
  } catch {
16514
16708
  }
@@ -16517,37 +16711,37 @@ function discoverNativeLinkedAppFonts(opts) {
16517
16711
  } catch {
16518
16712
  }
16519
16713
  try {
16520
- const configPath = import_node_path11.default.join(selectedAppDir, "react-native.config.js");
16521
- if (import_node_fs10.default.statSync(configPath).isFile()) {
16522
- const appConfigRequire = (0, import_node_module2.createRequire)(import_node_path11.default.join(selectedAppDir, "package.json"));
16714
+ const configPath = import_node_path12.default.join(selectedAppDir, "react-native.config.js");
16715
+ if (import_node_fs11.default.statSync(configPath).isFile()) {
16716
+ const appConfigRequire = (0, import_node_module2.createRequire)(import_node_path12.default.join(selectedAppDir, "package.json"));
16523
16717
  const rnConfig = appConfigRequire(configPath);
16524
16718
  const assets = rnConfig && typeof rnConfig === "object" ? Reflect.get(rnConfig, "assets") : null;
16525
16719
  if (Array.isArray(assets)) {
16526
16720
  for (const assetDir of assets) {
16527
16721
  if (typeof assetDir !== "string") continue;
16528
- const root = import_node_path11.default.resolve(selectedAppDir, assetDir);
16529
- const relativeRoot = import_node_path11.default.relative(selectedAppDir, root);
16530
- if (relativeRoot.startsWith("..") || import_node_path11.default.isAbsolute(relativeRoot)) continue;
16531
- collectFontFiles(root, relativeRoot.split(import_node_path11.default.sep).join("/"));
16722
+ const root = import_node_path12.default.resolve(selectedAppDir, assetDir);
16723
+ const relativeRoot = import_node_path12.default.relative(selectedAppDir, root);
16724
+ if (relativeRoot.startsWith("..") || import_node_path12.default.isAbsolute(relativeRoot)) continue;
16725
+ collectFontFiles(root, relativeRoot.split(import_node_path12.default.sep).join("/"));
16532
16726
  }
16533
16727
  }
16534
16728
  }
16535
16729
  } catch {
16536
16730
  }
16537
16731
  if (platform === "ios") {
16538
- const iosDir = import_node_path11.default.join(selectedAppDir, "ios");
16732
+ const iosDir = import_node_path12.default.join(selectedAppDir, "ios");
16539
16733
  try {
16540
- const targetDirs = import_node_fs10.default.readdirSync(iosDir, { withFileTypes: true }).filter(
16734
+ const targetDirs = import_node_fs11.default.readdirSync(iosDir, { withFileTypes: true }).filter(
16541
16735
  (entry) => entry.isDirectory() && !entry.isSymbolicLink() && entry.name !== "Pods" && entry.name !== "build" && !entry.name.startsWith(".")
16542
16736
  ).slice(0, 64);
16543
- const plistPaths = [import_node_path11.default.join(iosDir, "Info.plist")];
16737
+ const plistPaths = [import_node_path12.default.join(iosDir, "Info.plist")];
16544
16738
  for (const entry of targetDirs) {
16545
- plistPaths.push(import_node_path11.default.join(iosDir, entry.name, "Info.plist"));
16739
+ plistPaths.push(import_node_path12.default.join(iosDir, entry.name, "Info.plist"));
16546
16740
  }
16547
16741
  for (const plistPath of plistPaths) {
16548
16742
  let parsed;
16549
16743
  try {
16550
- parsed = parsePlistSource(import_node_fs10.default.readFileSync(plistPath, "utf8"));
16744
+ parsed = parsePlistSource(import_node_fs11.default.readFileSync(plistPath, "utf8"));
16551
16745
  } catch {
16552
16746
  continue;
16553
16747
  }
@@ -16557,21 +16751,21 @@ function discoverNativeLinkedAppFonts(opts) {
16557
16751
  for (const declaredFont of declaredFonts) {
16558
16752
  if (typeof declaredFont !== "string") continue;
16559
16753
  const candidates = [
16560
- import_node_path11.default.resolve(import_node_path11.default.dirname(plistPath), declaredFont),
16561
- import_node_path11.default.resolve(iosDir, declaredFont)
16754
+ import_node_path12.default.resolve(import_node_path12.default.dirname(plistPath), declaredFont),
16755
+ import_node_path12.default.resolve(iosDir, declaredFont)
16562
16756
  ];
16563
16757
  const fontPath = candidates.find((candidate) => {
16564
- const relative = import_node_path11.default.relative(selectedAppDir, candidate);
16565
- if (relative.startsWith("..") || import_node_path11.default.isAbsolute(relative)) return false;
16758
+ const relative = import_node_path12.default.relative(selectedAppDir, candidate);
16759
+ if (relative.startsWith("..") || import_node_path12.default.isAbsolute(relative)) return false;
16566
16760
  try {
16567
- return import_node_fs10.default.statSync(candidate).isFile();
16761
+ return import_node_fs11.default.statSync(candidate).isFile();
16568
16762
  } catch {
16569
16763
  return false;
16570
16764
  }
16571
16765
  });
16572
16766
  if (!fontPath) continue;
16573
16767
  fontPaths.push(
16574
- import_node_path11.default.relative(selectedAppDir, fontPath).split(import_node_path11.default.sep).join("/")
16768
+ import_node_path12.default.relative(selectedAppDir, fontPath).split(import_node_path12.default.sep).join("/")
16575
16769
  );
16576
16770
  }
16577
16771
  }
@@ -16610,13 +16804,13 @@ async function resolveMetroProjectRoot(bundleUrl, opts) {
16610
16804
  function encodeAppFontsWire(specs) {
16611
16805
  return specs.map((spec) => `${encodeURIComponent(spec.url)}::${encodeURIComponent(spec.family)}`).join(",");
16612
16806
  }
16613
- var import_node_fs10, import_node_module2, import_node_path11, import_config_plugin_fonts, require3;
16807
+ var import_node_fs11, import_node_module2, import_node_path12, import_config_plugin_fonts, require3;
16614
16808
  var init_app_fonts = __esm({
16615
16809
  "cli/app-fonts.ts"() {
16616
16810
  "use strict";
16617
- import_node_fs10 = __toESM(require("node:fs"), 1);
16811
+ import_node_fs11 = __toESM(require("node:fs"), 1);
16618
16812
  import_node_module2 = require("node:module");
16619
- import_node_path11 = __toESM(require("node:path"), 1);
16813
+ import_node_path12 = __toESM(require("node:path"), 1);
16620
16814
  import_config_plugin_fonts = require("sootsim-engine/engine/config-plugin-fonts");
16621
16815
  init_dev_server_scanner();
16622
16816
  init_plist();
@@ -16639,7 +16833,7 @@ function localOriginOf2(value) {
16639
16833
  }
16640
16834
  function hasPackageJson2(dir) {
16641
16835
  try {
16642
- return import_node_fs11.default.statSync(import_node_path12.default.join(dir, "package.json")).isFile();
16836
+ return import_node_fs12.default.statSync(import_node_path13.default.join(dir, "package.json")).isFile();
16643
16837
  } catch {
16644
16838
  return false;
16645
16839
  }
@@ -16648,7 +16842,7 @@ function discoverConfigPluginAppSplash(opts) {
16648
16842
  const metroOrigin = localOriginOf2(opts.bundleUrl);
16649
16843
  if (!metroOrigin) return null;
16650
16844
  const explicit = opts.repoDir || process.env.CONTRAST_REPO_DIR;
16651
- const startDir = explicit ? import_node_path12.default.resolve(explicit) : process.cwd();
16845
+ const startDir = explicit ? import_node_path13.default.resolve(explicit) : process.cwd();
16652
16846
  if (!explicit && !hasPackageJson2(startDir)) return null;
16653
16847
  const project = resolveAppProject(startDir, { trustStart: opts.exactProjectDir });
16654
16848
  const selectedAppDir = project?.appDir ?? null;
@@ -16682,13 +16876,13 @@ function discoverConfigPluginAppSplash(opts) {
16682
16876
  function encodeAppSplashWire(spec) {
16683
16877
  return encodeURIComponent(JSON.stringify(spec));
16684
16878
  }
16685
- var import_node_fs11, import_node_module3, import_node_path12, import_config_plugin_splash, require4;
16879
+ var import_node_fs12, import_node_module3, import_node_path13, import_config_plugin_splash, require4;
16686
16880
  var init_app_splash = __esm({
16687
16881
  "cli/app-splash.ts"() {
16688
16882
  "use strict";
16689
- import_node_fs11 = __toESM(require("node:fs"), 1);
16883
+ import_node_fs12 = __toESM(require("node:fs"), 1);
16690
16884
  import_node_module3 = require("node:module");
16691
- import_node_path12 = __toESM(require("node:path"), 1);
16885
+ import_node_path13 = __toESM(require("node:path"), 1);
16692
16886
  import_config_plugin_splash = require("sootsim-engine/engine/config-plugin-splash");
16693
16887
  init_app_project();
16694
16888
  require4 = (0, import_node_module3.createRequire)(__sootsim_import_meta_url);
@@ -17706,7 +17900,7 @@ function resolvePlaywright() {
17706
17900
  try {
17707
17901
  const modulePath = requireFromRoot.resolve(spec);
17708
17902
  const packageJsonPath = requireFromRoot.resolve(`${spec}/package.json`);
17709
- const parsed = JSON.parse((0, import_node_fs12.readFileSync)(packageJsonPath, "utf8"));
17903
+ const parsed = JSON.parse((0, import_node_fs13.readFileSync)(packageJsonPath, "utf8"));
17710
17904
  if (!isRecord3(parsed) || typeof parsed.version !== "string") continue;
17711
17905
  const bin = parsed.bin;
17712
17906
  let binPath = null;
@@ -17718,8 +17912,8 @@ function resolvePlaywright() {
17718
17912
  binPath = typeof named === "string" ? named : typeof first === "string" ? first : null;
17719
17913
  }
17720
17914
  if (!binPath) continue;
17721
- const cliPath = (0, import_node_path13.join)((0, import_node_path13.dirname)(packageJsonPath), binPath);
17722
- if (!(0, import_node_fs12.existsSync)(cliPath)) continue;
17915
+ const cliPath = (0, import_node_path14.join)((0, import_node_path14.dirname)(packageJsonPath), binPath);
17916
+ if (!(0, import_node_fs13.existsSync)(cliPath)) continue;
17723
17917
  return {
17724
17918
  spec,
17725
17919
  modulePath,
@@ -17758,7 +17952,7 @@ function probeBrowser(resolved, env) {
17758
17952
  return {
17759
17953
  ok: true,
17760
17954
  executablePath: parsed.executablePath,
17761
- exists: parsed.exists && (0, import_node_fs12.existsSync)(parsed.executablePath)
17955
+ exists: parsed.exists && (0, import_node_fs13.existsSync)(parsed.executablePath)
17762
17956
  };
17763
17957
  } catch (error) {
17764
17958
  return {
@@ -17797,14 +17991,14 @@ ${detail}` : ""}`
17797
17991
  }
17798
17992
  return { ok: true, executablePath: after.executablePath, installed: true };
17799
17993
  }
17800
- var import_node_child_process5, import_node_fs12, import_node_module4, import_node_path13, PLAYWRIGHT_BROWSER_PROBE;
17994
+ var import_node_child_process5, import_node_fs13, import_node_module4, import_node_path14, PLAYWRIGHT_BROWSER_PROBE;
17801
17995
  var init_playwright_provisioning = __esm({
17802
17996
  "cli/drivers/playwright-provisioning.ts"() {
17803
17997
  "use strict";
17804
17998
  import_node_child_process5 = require("node:child_process");
17805
- import_node_fs12 = require("node:fs");
17999
+ import_node_fs13 = require("node:fs");
17806
18000
  import_node_module4 = require("node:module");
17807
- import_node_path13 = require("node:path");
18001
+ import_node_path14 = require("node:path");
17808
18002
  PLAYWRIGHT_BROWSER_PROBE = `
17809
18003
  const { existsSync } = require('node:fs');
17810
18004
  const modulePath = process.env.SOOTSIM_PW_MODULE;
@@ -18956,14 +19150,14 @@ function assertExplicitBridgeLock(preferredPort, explicitPort, lock) {
18956
19150
  }
18957
19151
  async function ensureRuntimeForOpen() {
18958
19152
  const active = readActiveRuntime();
18959
- if (active && import_node_fs13.default.existsSync(runtimeDir(active))) return;
19153
+ if (active && import_node_fs14.default.existsSync(runtimeDir(active))) return;
18960
19154
  console.error(" installing rnx engine runtime...");
18961
19155
  const { runRuntime: runRuntime2 } = await Promise.resolve().then(() => (init_runtime(), runtime_exports));
18962
19156
  await runRuntime2(["install"], {});
18963
19157
  }
18964
19158
  function spawnSootsim(args) {
18965
19159
  const entry = process.argv[1];
18966
- if (entry && import_node_fs13.default.existsSync(entry)) {
19160
+ if (entry && import_node_fs14.default.existsSync(entry)) {
18967
19161
  return (0, import_node_child_process6.spawn)(process.execPath, [entry, ...args], {
18968
19162
  detached: true,
18969
19163
  stdio: "ignore",
@@ -19110,10 +19304,10 @@ async function buildResolvedBundleOpenUrl(target, baseUrl, opts = {}) {
19110
19304
  }
19111
19305
  function normalizeReplacementPath(filePath) {
19112
19306
  if (filePath.startsWith("~/")) {
19113
- return import_node_path14.default.join(import_node_os3.default.homedir(), filePath.slice(2));
19307
+ return import_node_path15.default.join(import_node_os3.default.homedir(), filePath.slice(2));
19114
19308
  }
19115
- if (import_node_path14.default.isAbsolute(filePath)) return filePath;
19116
- return import_node_path14.default.resolve(process.cwd(), filePath);
19309
+ if (import_node_path15.default.isAbsolute(filePath)) return filePath;
19310
+ return import_node_path15.default.resolve(process.cwd(), filePath);
19117
19311
  }
19118
19312
  function parseOpenRuntimeConfig(args) {
19119
19313
  const modules = {};
@@ -19132,7 +19326,7 @@ function parseOpenRuntimeConfig(args) {
19132
19326
  }
19133
19327
  const moduleName = raw.slice(0, eqIndex).trim();
19134
19328
  const filePath = normalizeReplacementPath(raw.slice(eqIndex + 1).trim());
19135
- if (!import_node_fs13.default.existsSync(filePath)) {
19329
+ if (!import_node_fs14.default.existsSync(filePath)) {
19136
19330
  console.error(` rnxsim open: replacement file not found: ${filePath}`);
19137
19331
  process.exit(1);
19138
19332
  }
@@ -19167,7 +19361,7 @@ async function resolveOpenAppProject(resolvedBundle) {
19167
19361
  return resolveAppProject(process.env.CONTRAST_REPO_DIR || process.cwd());
19168
19362
  }
19169
19363
  async function ensureConfiguredRuntime(version) {
19170
- if (import_node_fs13.default.existsSync(import_node_path14.default.join(runtimeDir(version), "index.html"))) return;
19364
+ if (import_node_fs14.default.existsSync(import_node_path15.default.join(runtimeDir(version), "index.html"))) return;
19171
19365
  console.error(` installing configured engine runtime ${version}...`);
19172
19366
  const result = await sootsimRuntime.install({
19173
19367
  version,
@@ -19531,7 +19725,7 @@ function playwrightHostPidForSim(sim) {
19531
19725
  function signalDriverLaunchConnected(result) {
19532
19726
  if (!result.connectAckFile) return false;
19533
19727
  try {
19534
- import_node_fs13.default.writeFileSync(
19728
+ import_node_fs14.default.writeFileSync(
19535
19729
  result.connectAckFile,
19536
19730
  `${JSON.stringify({ connectedAt: Date.now(), pid: result.pid ?? null })}
19537
19731
  `,
@@ -19545,19 +19739,19 @@ function signalDriverLaunchConnected(result) {
19545
19739
  function printDriverDiagnosticLogTail(result) {
19546
19740
  if (!result.diagnosticLogPath) return;
19547
19741
  try {
19548
- const detail = import_node_fs13.default.readFileSync(result.diagnosticLogPath, "utf8").trim();
19742
+ const detail = import_node_fs14.default.readFileSync(result.diagnosticLogPath, "utf8").trim();
19549
19743
  if (!detail) {
19550
- console.error(` ${import_node_path14.default.basename(result.diagnosticLogPath)} was empty`);
19744
+ console.error(` ${import_node_path15.default.basename(result.diagnosticLogPath)} was empty`);
19551
19745
  return;
19552
19746
  }
19553
19747
  const shown = detail.length > 4e3 ? `\u2026
19554
19748
  ${detail.slice(-4e3)}` : detail;
19555
- console.error(` ${import_node_path14.default.basename(result.diagnosticLogPath)}:`);
19749
+ console.error(` ${import_node_path15.default.basename(result.diagnosticLogPath)}:`);
19556
19750
  console.error(shown);
19557
19751
  } catch (err) {
19558
19752
  const message = err instanceof Error ? err.message : String(err);
19559
19753
  console.error(
19560
- ` failed to read ${import_node_path14.default.basename(result.diagnosticLogPath)}: ${message}`
19754
+ ` failed to read ${import_node_path15.default.basename(result.diagnosticLogPath)}: ${message}`
19561
19755
  );
19562
19756
  }
19563
19757
  }
@@ -19907,7 +20101,9 @@ async function runOpenCommand(args, opts = {}) {
19907
20101
  const viewportArg = args.find((_, i) => args[i - 1] === "--viewport");
19908
20102
  const viewportMatch = viewportArg ? /^(\d+)x(\d+)$/.exec(viewportArg) : null;
19909
20103
  if (viewportArg && !viewportMatch) {
19910
- console.error(` rnxsim open: --viewport must be <width>x<height>, got "${viewportArg}"`);
20104
+ console.error(
20105
+ ` rnxsim open: --viewport must be <width>x<height>, got "${viewportArg}"`
20106
+ );
19911
20107
  process.exit(1);
19912
20108
  }
19913
20109
  const explicitViewport = viewportMatch ? { width: Number(viewportMatch[1]), height: Number(viewportMatch[2]) } : void 0;
@@ -20361,14 +20557,14 @@ async function runCloseCommand(args, opts = {}) {
20361
20557
  bridge.close();
20362
20558
  }
20363
20559
  }
20364
- var import_node_child_process6, import_node_fs13, import_node_os3, import_node_path14, import_settings, import_ws5, DEFAULT_DRIVER_CONNECT_TIMEOUT_MS, DRIVER_CONNECT_INTERVAL_MS, DRIVER_CONNECT_HOST_GRACE_MS, OPEN_BRIDGE_SPAWN_TIMEOUT_MS, SHELL_APPS, DEEP_LINK_SCHEME_RE;
20560
+ var import_node_child_process6, import_node_fs14, import_node_os3, import_node_path15, import_settings, import_ws5, DEFAULT_DRIVER_CONNECT_TIMEOUT_MS, DRIVER_CONNECT_INTERVAL_MS, DRIVER_CONNECT_HOST_GRACE_MS, OPEN_BRIDGE_SPAWN_TIMEOUT_MS, SHELL_APPS, DEEP_LINK_SCHEME_RE;
20365
20561
  var init_control = __esm({
20366
20562
  "cli/commands/control.ts"() {
20367
20563
  "use strict";
20368
20564
  import_node_child_process6 = require("node:child_process");
20369
- import_node_fs13 = __toESM(require("node:fs"), 1);
20565
+ import_node_fs14 = __toESM(require("node:fs"), 1);
20370
20566
  import_node_os3 = __toESM(require("node:os"), 1);
20371
- import_node_path14 = __toESM(require("node:path"), 1);
20567
+ import_node_path15 = __toESM(require("node:path"), 1);
20372
20568
  import_settings = require("sootsim-engine/settings");
20373
20569
  import_ws5 = require("ws");
20374
20570
  init_dev_server_scanner();
@@ -21150,14 +21346,14 @@ process.stdin.on('end', async () => {
21150
21346
  function getBaseDir() {
21151
21347
  const explicit = process.env[SESSION_FILE_ENV];
21152
21348
  if (explicit?.trim()) {
21153
- return (0, import_node_path15.dirname)((0, import_node_path15.resolve)(explicit));
21349
+ return (0, import_node_path16.dirname)((0, import_node_path16.resolve)(explicit));
21154
21350
  }
21155
21351
  return electronUserDataDir();
21156
21352
  }
21157
21353
  function getSharedDesktopAuthFilePath() {
21158
21354
  const explicit = process.env[SESSION_FILE_ENV];
21159
- if (explicit?.trim()) return (0, import_node_path15.resolve)(explicit);
21160
- return (0, import_node_path15.join)(getBaseDir(), "desktop-auth.json");
21355
+ if (explicit?.trim()) return (0, import_node_path16.resolve)(explicit);
21356
+ return (0, import_node_path16.join)(getBaseDir(), "desktop-auth.json");
21161
21357
  }
21162
21358
  function normalizeUser(input) {
21163
21359
  if (!input || typeof input !== "object") return null;
@@ -21204,26 +21400,26 @@ function normalizeSession(input) {
21204
21400
  }
21205
21401
  function readSharedDesktopAuthSession() {
21206
21402
  const filepath = getSharedDesktopAuthFilePath();
21207
- if (!(0, import_node_fs14.existsSync)(filepath)) return null;
21403
+ if (!(0, import_node_fs15.existsSync)(filepath)) return null;
21208
21404
  try {
21209
- const parsed = JSON.parse((0, import_node_fs14.readFileSync)(filepath, "utf8"));
21405
+ const parsed = JSON.parse((0, import_node_fs15.readFileSync)(filepath, "utf8"));
21210
21406
  const normalized = normalizeSession(parsed);
21211
21407
  if (!normalized) {
21212
- (0, import_node_fs14.rmSync)(filepath, { force: true });
21408
+ (0, import_node_fs15.rmSync)(filepath, { force: true });
21213
21409
  return null;
21214
21410
  }
21215
21411
  return normalized;
21216
21412
  } catch {
21217
- (0, import_node_fs14.rmSync)(filepath, { force: true });
21413
+ (0, import_node_fs15.rmSync)(filepath, { force: true });
21218
21414
  return null;
21219
21415
  }
21220
21416
  }
21221
- var import_node_fs14, import_node_path15, SESSION_VERSION, SESSION_FILE_ENV, DEFAULT_ORIGIN;
21417
+ var import_node_fs15, import_node_path16, SESSION_VERSION, SESSION_FILE_ENV, DEFAULT_ORIGIN;
21222
21418
  var init_shared_session = __esm({
21223
21419
  "src/auth/shared-session.ts"() {
21224
21420
  "use strict";
21225
- import_node_fs14 = require("node:fs");
21226
- import_node_path15 = require("node:path");
21421
+ import_node_fs15 = require("node:fs");
21422
+ import_node_path16 = require("node:path");
21227
21423
  init_home_paths();
21228
21424
  SESSION_VERSION = 1;
21229
21425
  SESSION_FILE_ENV = "SOOTSIM_SHARED_AUTH_FILE";
@@ -21486,16 +21682,16 @@ function resolveFlowScreenshotPath(screenshotDir, name2) {
21486
21682
  }
21487
21683
  function resolveProjectRelativeFlowBase(flowDir) {
21488
21684
  if (!flowDir) return process.cwd();
21489
- return path14.basename(flowDir) === ".maestro" ? path14.dirname(flowDir) : flowDir;
21685
+ return path15.basename(flowDir) === ".maestro" ? path15.dirname(flowDir) : flowDir;
21490
21686
  }
21491
21687
  function resolveFlowScreenshotPathWithMode(screenshotDir, name2, opts) {
21492
21688
  const normalizedName = name2.endsWith(".png") ? name2 : `${name2}.png`;
21493
- if (path14.isAbsolute(normalizedName)) return normalizedName;
21689
+ if (path15.isAbsolute(normalizedName)) return normalizedName;
21494
21690
  if (opts.mode === "flow" && /[\\/]/.test(normalizedName)) {
21495
21691
  const baseDir = normalizedName.startsWith("./") || normalizedName.startsWith("../") ? opts.flowDir ?? process.cwd() : resolveProjectRelativeFlowBase(opts.flowDir);
21496
- return path14.resolve(baseDir, normalizedName);
21692
+ return path15.resolve(baseDir, normalizedName);
21497
21693
  }
21498
- return path14.join(screenshotDir, normalizedName);
21694
+ return path15.join(screenshotDir, normalizedName);
21499
21695
  }
21500
21696
  function normalizeFlowScreenshotSpec(step) {
21501
21697
  if (typeof step === "string") {
@@ -21552,12 +21748,12 @@ function parseFlowSteps(content) {
21552
21748
  const parsed = import_yaml.default.parse(candidate);
21553
21749
  return Array.isArray(parsed) ? parsed : [];
21554
21750
  }
21555
- var fs15, path14, import_settings3, import_yaml, DEFAULT_TIMEOUT, FLOW_TIMEOUT_SCALE, scaledTimeout, SCREEN_W, SCREEN_H, SootSimBridgeFlowRunner;
21751
+ var fs16, path15, import_settings3, import_yaml, DEFAULT_TIMEOUT, FLOW_TIMEOUT_SCALE, scaledTimeout, SCREEN_W, SCREEN_H, SootSimBridgeFlowRunner;
21556
21752
  var init_bridge_flow_runner = __esm({
21557
21753
  "cli/bridge-flow-runner.ts"() {
21558
21754
  "use strict";
21559
- fs15 = __toESM(require("fs"), 1);
21560
- path14 = __toESM(require("path"), 1);
21755
+ fs16 = __toESM(require("fs"), 1);
21756
+ path15 = __toESM(require("path"), 1);
21561
21757
  import_settings3 = require("sootsim-engine/settings");
21562
21758
  import_yaml = __toESM(require("yaml"), 1);
21563
21759
  init_frame_compose();
@@ -21668,6 +21864,15 @@ var init_bridge_flow_runner = __esm({
21668
21864
  args
21669
21865
  });
21670
21866
  }
21867
+ // ordered input batch executed inside the page in one round-trip; the
21868
+ // engine emits agent-cursor actions for every step natively.
21869
+ async perform(steps) {
21870
+ return this.bridge.send({
21871
+ type: "perform",
21872
+ simId: this.opts.simId,
21873
+ steps
21874
+ });
21875
+ }
21671
21876
  async waitForTree(timeout = DEFAULT_TIMEOUT) {
21672
21877
  const deadline = Date.now() + scaledTimeout(timeout);
21673
21878
  const startedAt = Date.now();
@@ -21845,14 +22050,14 @@ var init_bridge_flow_runner = __esm({
21845
22050
  if (chunks.length === 0) {
21846
22051
  throw new Error("recording requested but no video buffer was produced");
21847
22052
  }
21848
- fs15.mkdirSync(this.opts.recordingOutputDir, { recursive: true });
22053
+ fs16.mkdirSync(this.opts.recordingOutputDir, { recursive: true });
21849
22054
  const videoMime = stopped.mime || this.opts.recordingFormat || "video/webm";
21850
22055
  const extension = videoMime.includes("mp4") ? "mp4" : "webm";
21851
- const outputPath = path14.join(
22056
+ const outputPath = path15.join(
21852
22057
  this.opts.recordingOutputDir,
21853
22058
  `sootsim-${Date.now()}.${extension}`
21854
22059
  );
21855
- fs15.writeFileSync(outputPath, Buffer.concat(chunks));
22060
+ fs16.writeFileSync(outputPath, Buffer.concat(chunks));
21856
22061
  return outputPath;
21857
22062
  }
21858
22063
  async waitForRecordingTail(opts) {
@@ -22186,20 +22391,14 @@ var init_bridge_flow_runner = __esm({
22186
22391
  await sleep3(300);
22187
22392
  }
22188
22393
  async doubleTapAtCoords(x, y, gapMs = 80) {
22189
- const waitMs = Math.max(0, Math.round(gapMs));
22190
- const ok = await this.evaluate(`(async () => {
22191
- const interact = window.__sootsimInteract
22192
- if (interact?.doubleTap) {
22193
- return !!(await interact.doubleTap(${x}, ${y}, ${waitMs}))
22194
- }
22195
- if (!interact?.tap) return false
22196
- const first = await interact.tap(${x}, ${y})
22197
- if (!first || first.hit === false) return false
22198
- await new Promise((resolve) => setTimeout(resolve, ${waitMs}))
22199
- const second = await interact.tap(${x}, ${y})
22200
- return !!second && second.hit !== false
22201
- })()`);
22202
- if (!ok) throw new Error(`doubleTapAtCoords failed at (${x}, ${y})`);
22394
+ const result = await this.perform([
22395
+ { type: "doubleTap", x, y, gapMs: Math.max(0, Math.round(gapMs)) }
22396
+ ]);
22397
+ if (!result?.ok) {
22398
+ throw new Error(
22399
+ `doubleTapAtCoords failed at (${x}, ${y}): ${result?.error ?? "double tap missed"}`
22400
+ );
22401
+ }
22203
22402
  await sleep3(300);
22204
22403
  }
22205
22404
  async assertVisible(target) {
@@ -22361,7 +22560,7 @@ var init_bridge_flow_runner = __esm({
22361
22560
  flowDir: this.opts.flowDir
22362
22561
  }
22363
22562
  );
22364
- fs15.mkdirSync(path14.dirname(outputPath), { recursive: true });
22563
+ fs16.mkdirSync(path15.dirname(outputPath), { recursive: true });
22365
22564
  const layers = spec.layers ?? this.opts.screenshotLayers;
22366
22565
  const screenshotRequest = {
22367
22566
  type: "screenshot",
@@ -22377,11 +22576,11 @@ var init_bridge_flow_runner = __esm({
22377
22576
  throw new Error("could not read current device model for framed screenshot");
22378
22577
  }
22379
22578
  const framed = await composeFramedScreenshot(rawBuffer, model);
22380
- fs15.writeFileSync(outputPath, framed);
22579
+ fs16.writeFileSync(outputPath, framed);
22381
22580
  console.log(`[flow] screenshot: ${outputPath} (frame: ${model})`);
22382
22581
  return outputPath;
22383
22582
  }
22384
- fs15.writeFileSync(outputPath, rawBuffer);
22583
+ fs16.writeFileSync(outputPath, rawBuffer);
22385
22584
  console.log(`[flow] screenshot: ${outputPath}`);
22386
22585
  return outputPath;
22387
22586
  }
@@ -22397,22 +22596,22 @@ var init_bridge_flow_runner = __esm({
22397
22596
  return model;
22398
22597
  }
22399
22598
  async captureScreenshot(outputPath) {
22400
- fs15.mkdirSync(path14.dirname(outputPath), { recursive: true });
22599
+ fs16.mkdirSync(path15.dirname(outputPath), { recursive: true });
22401
22600
  const dataUrl = await this.bridge.send({
22402
22601
  type: "screenshot",
22403
22602
  simId: this.opts.simId
22404
22603
  });
22405
22604
  const base64 = dataUrl.replace(/^data:image\/png;base64,/, "");
22406
- fs15.writeFileSync(outputPath, Buffer.from(base64, "base64"));
22605
+ fs16.writeFileSync(outputPath, Buffer.from(base64, "base64"));
22407
22606
  }
22408
22607
  // writes a per-failure debug bundle: screenshot + structured describe
22409
22608
  // + a11y tree + console errors/warnings + failed network requests +
22410
22609
  // shell state, all under one directory so post-mortem is one grep away.
22411
22610
  async captureFailureBundle(outputDir, context) {
22412
- fs15.mkdirSync(outputDir, { recursive: true });
22611
+ fs16.mkdirSync(outputDir, { recursive: true });
22413
22612
  const write = (name2, content) => {
22414
22613
  try {
22415
- fs15.writeFileSync(path14.join(outputDir, name2), content);
22614
+ fs16.writeFileSync(path15.join(outputDir, name2), content);
22416
22615
  } catch {
22417
22616
  }
22418
22617
  };
@@ -22540,14 +22739,19 @@ var init_bridge_flow_runner = __esm({
22540
22739
  await this.drag(fromX, fromY, toX, toY, steps, 16);
22541
22740
  }
22542
22741
  async drag(fromX, fromY, toX, toY, steps = 12, stepMs = 16) {
22543
- const result = await this.evaluate(`(async () => {
22544
- const interact = window.__sootsimInteract
22545
- if (!interact?.drag) return { ok: false }
22546
- const value = await interact.drag(${fromX}, ${fromY}, ${toX}, ${toY}, ${Math.max(1, Math.round(steps))}, ${Math.max(0, Math.round(stepMs))})
22547
- return { ok: !!value, value }
22548
- })()`);
22742
+ const result = await this.perform([
22743
+ {
22744
+ type: "drag",
22745
+ fromX,
22746
+ fromY,
22747
+ toX,
22748
+ toY,
22749
+ steps: Math.max(1, Math.round(steps)),
22750
+ stepMs: Math.max(0, Math.round(stepMs))
22751
+ }
22752
+ ]);
22549
22753
  if (!result?.ok) {
22550
- throw new Error("drag failed");
22754
+ throw new Error(`drag failed: ${result?.error ?? "drag missed"}`);
22551
22755
  }
22552
22756
  await sleep3(300);
22553
22757
  }
@@ -22596,18 +22800,23 @@ var init_bridge_flow_runner = __esm({
22596
22800
  await sleep3(250);
22597
22801
  }
22598
22802
  async pinch(opts) {
22599
- const result = await this.evaluate(`(async () => {
22600
- const interact = window.__sootsimInteract
22601
- if (!interact?.pinch) return { ok: false, reason: 'no interact.pinch' }
22602
- const ok = await interact.pinch(
22603
- ${opts.from[0]}, ${opts.from[1]}, ${opts.from[2]}, ${opts.from[3]},
22604
- ${opts.to[0]}, ${opts.to[1]}, ${opts.to[2]}, ${opts.to[3]},
22605
- ${opts.steps || 12}, ${opts.stepMs || 16}
22606
- )
22607
- return { ok: !!ok, reason: ok ? null : 'pinch returned false' }
22608
- })()`);
22803
+ const result = await this.perform([
22804
+ {
22805
+ type: "pinch",
22806
+ fromX1: opts.from[0],
22807
+ fromY1: opts.from[1],
22808
+ fromX2: opts.from[2],
22809
+ fromY2: opts.from[3],
22810
+ toX1: opts.to[0],
22811
+ toY1: opts.to[1],
22812
+ toX2: opts.to[2],
22813
+ toY2: opts.to[3],
22814
+ steps: opts.steps || 12,
22815
+ stepMs: opts.stepMs || 16
22816
+ }
22817
+ ]);
22609
22818
  if (!result?.ok) {
22610
- throw new Error(`pinch failed: ${result?.reason || "unknown error"}`);
22819
+ throw new Error(`pinch failed: ${result?.error || "unknown error"}`);
22611
22820
  }
22612
22821
  await sleep3(250);
22613
22822
  }
@@ -22684,43 +22893,31 @@ var init_bridge_flow_runner = __esm({
22684
22893
  }
22685
22894
  throw new Error("extendedWaitUntil timed out");
22686
22895
  }
22687
- // relaunch the guest app for a clearState/launchApp step. two phases:
22688
- // 1. clear tenant-scoped storage through the engine bridge (best-effort).
22689
- // 2. ALWAYS hard-reload the page to reset the tenant worker.
22690
- // phase 2 is the important one for a long suite: the engine soft reload alone
22691
- // keeps the same worker alive across every flow, so a saturated event loop /
22692
- // accumulated state from one flow's post-PDS-reset auth-failure storm carries
22693
- // into the next and degrades until the worker wedges or the tab crashes (the
22694
- // failures clustered in the tail of a 21-flow run). a full page reload starts
22695
- // each flow on a fresh worker — worker-independent, so it also recovers a
22696
- // worker the bridge clear couldn't even ack. storage persists across a page
22697
- // reload, so the clear in phase 1 is what actually logs the guest out.
22698
- async reloadGuestApp(resetStorage) {
22699
- if (resetStorage) {
22700
- const ATTEMPTS = 3;
22701
- const PER_ATTEMPT_MS = 2e4;
22702
- let cleared = false;
22703
- for (let attempt = 0; attempt < ATTEMPTS && !cleared; attempt++) {
22704
- try {
22705
- cleared = await this.evaluate(
22706
- resetGuestAppStateEval(true),
22707
- PER_ATTEMPT_MS
22708
- );
22709
- } catch {
22710
- await sleep3(1e3);
22711
- }
22712
- }
22713
- if (!cleared) {
22714
- console.warn(
22715
- " warn: tenant storage clear did not ack \u2014 hard-reloading anyway (worker reset follows)"
22716
- );
22717
- }
22718
- }
22896
+ async reloadGuestApp() {
22719
22897
  await this.evaluate("window.location.reload()").catch(() => {
22720
22898
  });
22721
22899
  await sleep3(500);
22722
22900
  await this.refreshSimId();
22723
22901
  }
22902
+ async resetGuestAppData() {
22903
+ const reset = await this.bridge.send(
22904
+ {
22905
+ type: "reset",
22906
+ simId: this.opts.simId,
22907
+ resetOptions: { strategy: "data" }
22908
+ },
22909
+ { timeoutMs: 12e4 }
22910
+ );
22911
+ if (!reset.ok) {
22912
+ throw new Error(reset.error ?? "guest app data reset failed");
22913
+ }
22914
+ if (!reset.relaunched) {
22915
+ throw new Error("guest app data reset did not relaunch the app");
22916
+ }
22917
+ if (reset.workerReloaded !== true) {
22918
+ throw new Error("guest app data reset did not replace the tenant worker");
22919
+ }
22920
+ }
22724
22921
  async softReloadGuestApp(resetStorage) {
22725
22922
  const reloaded = await this.evaluate(
22726
22923
  resetGuestAppStateEval(resetStorage),
@@ -22769,7 +22966,11 @@ var init_bridge_flow_runner = __esm({
22769
22966
  await this.waitForTree(3e4);
22770
22967
  } else {
22771
22968
  this.firstLaunchDone = true;
22772
- await this.reloadGuestApp(wantsClear);
22969
+ if (wantsClear) {
22970
+ await this.resetGuestAppData();
22971
+ } else {
22972
+ await this.reloadGuestApp();
22973
+ }
22773
22974
  await this.waitForTree(12e4);
22774
22975
  if (this.opts.onAfterLaunch) {
22775
22976
  try {
@@ -22882,13 +23083,12 @@ var init_bridge_flow_runner = __esm({
22882
23083
  // nearest equivalent to killing and later re-launching the tenant runtime.
22883
23084
  // State clearing belongs to clearState / launchApp({ clearState: true }).
22884
23085
  async stopApp() {
22885
- await this.reloadGuestApp(false);
23086
+ await this.reloadGuestApp();
22886
23087
  await this.rearmCaptureAfterRuntimeReload();
22887
23088
  }
22888
- // maestro clearState: wipe app-owned storage and relaunch fresh. routes
22889
- // through the same engine soft-reload launchApp uses under `clearState: true`.
23089
+ // maestro clearState: wipe app-owned storage and relaunch fresh.
22890
23090
  async clearState() {
22891
- await this.reloadGuestApp(true);
23091
+ await this.resetGuestAppData();
22892
23092
  await this.rearmCaptureAfterRuntimeReload();
22893
23093
  }
22894
23094
  // maestro clearKeychain: there is no real keychain in sootsim's browser
@@ -22930,8 +23130,8 @@ var init_bridge_flow_runner = __esm({
22930
23130
  runScript(target) {
22931
23131
  const spec = typeof target === "string" ? { file: target } : target;
22932
23132
  if (!spec.file) throw new Error("runScript requires a file");
22933
- const scriptPath = path14.resolve(this.opts.flowDir, spec.file);
22934
- const source = fs15.readFileSync(scriptPath, "utf8");
23133
+ const scriptPath = path15.resolve(this.opts.flowDir, spec.file);
23134
+ const source = fs16.readFileSync(scriptPath, "utf8");
22935
23135
  this.js.evaluate(source, { env: spec.env });
22936
23136
  console.log(`[flow] runScript: ${spec.file} done`);
22937
23137
  }
@@ -23225,10 +23425,10 @@ var init_bridge_flow_runner = __esm({
23225
23425
  for (const [k, v] of Object.entries(env)) this.js.putEnv(k, v);
23226
23426
  }
23227
23427
  if (file) {
23228
- const flowPath = path14.resolve(this.opts.flowDir, file);
23229
- const flowContent = fs15.readFileSync(flowPath, "utf8");
23428
+ const flowPath = path15.resolve(this.opts.flowDir, file);
23429
+ const flowContent = fs16.readFileSync(flowPath, "utf8");
23230
23430
  const previousDir = this.opts.flowDir;
23231
- this.opts.flowDir = path14.dirname(flowPath);
23431
+ this.opts.flowDir = path15.dirname(flowPath);
23232
23432
  try {
23233
23433
  await this.runFlow(parseFlowSteps(flowContent));
23234
23434
  } finally {
@@ -23383,8 +23583,8 @@ __export(registry_exports, {
23383
23583
  skillRegistry: () => skillRegistry
23384
23584
  });
23385
23585
  module.exports = __toCommonJS(registry_exports);
23386
- var fs16 = __toESM(require("fs"), 1);
23387
- var path15 = __toESM(require("path"), 1);
23586
+ var fs17 = __toESM(require("fs"), 1);
23587
+ var path16 = __toESM(require("path"), 1);
23388
23588
 
23389
23589
  // src/skills/builtin/a11y-review.ts
23390
23590
  init_reap_browser();
@@ -23403,8 +23603,8 @@ var skill = {
23403
23603
  },
23404
23604
  async execute(params, context) {
23405
23605
  const { chromium } = await import("playwright");
23406
- const path16 = await import("path");
23407
- const fs17 = await import("fs");
23606
+ const path17 = await import("path");
23607
+ const fs18 = await import("fs");
23408
23608
  const browser = await launchReapedChrome((options) => chromium.launch(options), {
23409
23609
  headless: true
23410
23610
  });
@@ -23459,9 +23659,9 @@ var skill = {
23459
23659
  warnings: issues.filter((i) => i.severity === "warning").length,
23460
23660
  tree: typeof tree === "string" ? tree : JSON.stringify(tree)
23461
23661
  };
23462
- const reportPath = path16.join(context.outputDir, "a11y-report.json");
23463
- fs17.mkdirSync(path16.dirname(reportPath), { recursive: true });
23464
- fs17.writeFileSync(reportPath, JSON.stringify(report, null, 2));
23662
+ const reportPath = path17.join(context.outputDir, "a11y-report.json");
23663
+ fs18.mkdirSync(path17.dirname(reportPath), { recursive: true });
23664
+ fs18.writeFileSync(reportPath, JSON.stringify(report, null, 2));
23465
23665
  return {
23466
23666
  success: issues.filter((i) => i.severity === "error").length === 0,
23467
23667
  message: `${report.errors} errors, ${report.warnings} warnings across ${report.nodeCount} nodes`,
@@ -23494,14 +23694,14 @@ var skill2 = {
23494
23694
  }
23495
23695
  },
23496
23696
  async execute(params, context) {
23497
- const fs17 = await import("node:fs");
23498
- const path16 = await import("node:path");
23697
+ const fs18 = await import("node:fs");
23698
+ const path17 = await import("node:path");
23499
23699
  const { COMPAT_RESULT_CAVEAT: COMPAT_RESULT_CAVEAT2, formatPackageStatus: formatPackageStatus2, formatScanCounts: formatScanCounts2, scanDeps: scanDeps2 } = await Promise.resolve().then(() => (init_web(), web_exports));
23500
- const packagePath = path16.join(context.projectDir, "package.json");
23501
- if (!fs17.existsSync(packagePath)) {
23700
+ const packagePath = path17.join(context.projectDir, "package.json");
23701
+ if (!fs18.existsSync(packagePath)) {
23502
23702
  return { success: false, message: "No package.json found" };
23503
23703
  }
23504
- const parsed = JSON.parse(fs17.readFileSync(packagePath, "utf8"));
23704
+ const parsed = JSON.parse(fs18.readFileSync(packagePath, "utf8"));
23505
23705
  if (!parsed || typeof parsed !== "object") {
23506
23706
  return { success: false, message: "package.json is not an object" };
23507
23707
  }
@@ -23513,9 +23713,9 @@ var skill2 = {
23513
23713
  }) : scanDeps2(parsed);
23514
23714
  const packageResult = params.packageName ? scan.packages[0] : void 0;
23515
23715
  const message = packageResult ? `${params.packageName}: ${formatPackageStatus2(packageResult)}. ${COMPAT_RESULT_CAVEAT2}` : `${formatScanCounts2(scan)}. ${COMPAT_RESULT_CAVEAT2}`;
23516
- const reportPath = path16.join(context.outputDir, "compat-report.json");
23517
- fs17.mkdirSync(path16.dirname(reportPath), { recursive: true });
23518
- fs17.writeFileSync(reportPath, `${JSON.stringify(scan, null, 2)}
23716
+ const reportPath = path17.join(context.outputDir, "compat-report.json");
23717
+ fs18.mkdirSync(path17.dirname(reportPath), { recursive: true });
23718
+ fs18.writeFileSync(reportPath, `${JSON.stringify(scan, null, 2)}
23519
23719
  `);
23520
23720
  return {
23521
23721
  success: true,
@@ -23559,8 +23759,8 @@ var skill3 = {
23559
23759
  record: { type: "boolean", description: "Record video of the flow" }
23560
23760
  },
23561
23761
  async execute(params, context) {
23562
- const fs17 = await import("fs");
23563
- const path16 = await import("path");
23762
+ const fs18 = await import("fs");
23763
+ const path17 = await import("path");
23564
23764
  const yaml2 = await import("yaml");
23565
23765
  const { DEFAULT_SOOTSIM_BRIDGE_PORT: DEFAULT_SOOTSIM_BRIDGE_PORT2 } = await Promise.resolve().then(() => (init_bridge_constants(), bridge_constants_exports));
23566
23766
  const { SootSimBridgeHost: SootSimBridgeHost2 } = await Promise.resolve().then(() => (init_bridge_host(), bridge_host_exports));
@@ -23570,7 +23770,7 @@ var skill3 = {
23570
23770
  const host = new SootSimBridgeHost2({ port: DEFAULT_SOOTSIM_BRIDGE_PORT2 });
23571
23771
  host.start({ silent: true });
23572
23772
  try {
23573
- const flowContent = fs17.readFileSync(params.flowPath, "utf8");
23773
+ const flowContent = fs18.readFileSync(params.flowPath, "utf8");
23574
23774
  const fmMatch = flowContent.match(/^---\n([\s\S]*?)\n---\n?/);
23575
23775
  const stepsBody = fmMatch ? flowContent.slice(fmMatch[0].length) : flowContent;
23576
23776
  const steps = yaml2.parse(stepsBody);
@@ -23597,7 +23797,7 @@ var skill3 = {
23597
23797
  });
23598
23798
  const driver = new SootSimBridgeFlowRunner2(bridge, {
23599
23799
  screenshotDir: context.outputDir,
23600
- flowDir: path16.dirname(path16.resolve(params.flowPath)),
23800
+ flowDir: path17.dirname(path17.resolve(params.flowPath)),
23601
23801
  simId,
23602
23802
  recordingOutputDir: params.record ? context.outputDir : void 0
23603
23803
  });
@@ -23619,7 +23819,7 @@ var skill3 = {
23619
23819
  simId,
23620
23820
  videoPath: videoPath || void 0
23621
23821
  },
23622
- artifacts: videoPath ? [{ type: "video", name: path16.basename(videoPath), path: videoPath }] : void 0
23822
+ artifacts: videoPath ? [{ type: "video", name: path17.basename(videoPath), path: videoPath }] : void 0
23623
23823
  };
23624
23824
  } finally {
23625
23825
  try {
@@ -23666,8 +23866,8 @@ var skill4 = {
23666
23866
  },
23667
23867
  async execute(params, context) {
23668
23868
  const { chromium } = await import("playwright");
23669
- const path16 = await import("path");
23670
- const fs17 = await import("fs");
23869
+ const path17 = await import("path");
23870
+ const fs18 = await import("fs");
23671
23871
  const browser = await launchReapedChrome((options) => chromium.launch(options), {
23672
23872
  headless: true
23673
23873
  });
@@ -23693,9 +23893,9 @@ var skill4 = {
23693
23893
  ...profile,
23694
23894
  fps: syncCount > 0 ? (syncCount / (duration / 1e3)).toFixed(1) : "0"
23695
23895
  };
23696
- const reportPath = path16.join(context.outputDir, "perf-report.json");
23697
- fs17.mkdirSync(path16.dirname(reportPath), { recursive: true });
23698
- fs17.writeFileSync(reportPath, JSON.stringify(report, null, 2));
23896
+ const reportPath = path17.join(context.outputDir, "perf-report.json");
23897
+ fs18.mkdirSync(path17.dirname(reportPath), { recursive: true });
23898
+ fs18.writeFileSync(reportPath, JSON.stringify(report, null, 2));
23699
23899
  return {
23700
23900
  success: true,
23701
23901
  message: `${report.fps} fps, ${profile.avgSyncMs?.toFixed(1)}ms avg sync, ${nodeCount} nodes`,
@@ -23728,8 +23928,8 @@ var skill5 = {
23728
23928
  },
23729
23929
  async execute(params, context) {
23730
23930
  const { chromium } = await import("playwright");
23731
- const path16 = await import("path");
23732
- const fs17 = await import("fs");
23931
+ const path17 = await import("path");
23932
+ const fs18 = await import("fs");
23733
23933
  const browser = await launchReapedChrome((options) => chromium.launch(options), {
23734
23934
  headless: true
23735
23935
  });
@@ -23737,8 +23937,8 @@ var skill5 = {
23737
23937
  try {
23738
23938
  await page.goto(context.url, { waitUntil: "networkidle" });
23739
23939
  await page.waitForTimeout(2e3);
23740
- const outputPath = path16.join(context.outputDir, `${params.name}.png`);
23741
- fs17.mkdirSync(path16.dirname(outputPath), { recursive: true });
23940
+ const outputPath = path17.join(context.outputDir, `${params.name}.png`);
23941
+ fs18.mkdirSync(path17.dirname(outputPath), { recursive: true });
23742
23942
  await page.screenshot({ path: outputPath });
23743
23943
  return {
23744
23944
  success: true,
@@ -23779,8 +23979,8 @@ var skill6 = {
23779
23979
  },
23780
23980
  async execute(params, context) {
23781
23981
  const { chromium } = await import("playwright");
23782
- const path16 = await import("path");
23783
- const fs17 = await import("fs");
23982
+ const path17 = await import("path");
23983
+ const fs18 = await import("fs");
23784
23984
  const browser = await launchReapedChrome((options) => chromium.launch(options), {
23785
23985
  headless: true
23786
23986
  });
@@ -23788,14 +23988,14 @@ var skill6 = {
23788
23988
  try {
23789
23989
  await page.goto(context.url, { waitUntil: "networkidle" });
23790
23990
  await page.waitForTimeout(2e3);
23791
- const currentPath = path16.join(context.outputDir, `${params.name}-current.png`);
23792
- fs17.mkdirSync(path16.dirname(currentPath), { recursive: true });
23991
+ const currentPath = path17.join(context.outputDir, `${params.name}-current.png`);
23992
+ fs18.mkdirSync(path17.dirname(currentPath), { recursive: true });
23793
23993
  await page.screenshot({ path: currentPath });
23794
23994
  try {
23795
23995
  const { PNG } = await import("pngjs");
23796
23996
  const pixelmatch = (await import("pixelmatch")).default;
23797
- const baseline = PNG.sync.read(fs17.readFileSync(params.baseline));
23798
- const current = PNG.sync.read(fs17.readFileSync(currentPath));
23997
+ const baseline = PNG.sync.read(fs18.readFileSync(params.baseline));
23998
+ const current = PNG.sync.read(fs18.readFileSync(currentPath));
23799
23999
  const { width, height } = baseline;
23800
24000
  const diff = new PNG({ width, height });
23801
24001
  const numDiffPixels = pixelmatch(
@@ -23806,8 +24006,8 @@ var skill6 = {
23806
24006
  height,
23807
24007
  { threshold: params.threshold || 0.1 }
23808
24008
  );
23809
- const diffPath = path16.join(context.outputDir, `${params.name}-diff.png`);
23810
- fs17.writeFileSync(diffPath, PNG.sync.write(diff));
24009
+ const diffPath = path17.join(context.outputDir, `${params.name}-diff.png`);
24010
+ fs18.writeFileSync(diffPath, PNG.sync.write(diff));
23811
24011
  const totalPixels = width * height;
23812
24012
  const diffPercent = (numDiffPixels / totalPixels * 100).toFixed(2);
23813
24013
  return {
@@ -23871,10 +24071,10 @@ var skillRegistry = {
23871
24071
  },
23872
24072
  // load skills from a project manifest (sootsim-skills.json)
23873
24073
  async loadFromManifest(projectDir) {
23874
- const manifestPath = path15.join(projectDir, "sootsim-skills.json");
23875
- if (!fs16.existsSync(manifestPath)) return;
24074
+ const manifestPath = path16.join(projectDir, "sootsim-skills.json");
24075
+ if (!fs17.existsSync(manifestPath)) return;
23876
24076
  try {
23877
- const manifest = JSON.parse(fs16.readFileSync(manifestPath, "utf8"));
24077
+ const manifest = JSON.parse(fs17.readFileSync(manifestPath, "utf8"));
23878
24078
  if (Array.isArray(manifest.skills)) {
23879
24079
  for (const entry of manifest.skills) {
23880
24080
  if (typeof entry === "string") {
@@ -23885,7 +24085,7 @@ var skillRegistry = {
23885
24085
  }
23886
24086
  } else if (entry.file) {
23887
24087
  try {
23888
- const mod = await import(path15.resolve(projectDir, entry.file));
24088
+ const mod = await import(path16.resolve(projectDir, entry.file));
23889
24089
  if (mod.skill) this.register(mod.skill);
23890
24090
  } catch {
23891
24091
  }
@@ -23898,10 +24098,10 @@ var skillRegistry = {
23898
24098
  // auto-discover skills based on project dependencies
23899
24099
  suggestSkills(projectDir) {
23900
24100
  const suggestions = [];
23901
- const pkgPath = path15.join(projectDir, "package.json");
23902
- if (!fs16.existsSync(pkgPath)) return suggestions;
24101
+ const pkgPath = path16.join(projectDir, "package.json");
24102
+ if (!fs17.existsSync(pkgPath)) return suggestions;
23903
24103
  try {
23904
- const pkg = JSON.parse(fs16.readFileSync(pkgPath, "utf8"));
24104
+ const pkg = JSON.parse(fs17.readFileSync(pkgPath, "utf8"));
23905
24105
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
23906
24106
  if (deps["detox"] || deps["maestro"]) {
23907
24107
  suggestions.push("maestro-test");