rnxsim 0.1.369 → 0.1.371

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 (265) hide show
  1. package/cli/auth.ts +2 -1
  2. package/cli/bin.ts +283 -273
  3. package/cli/commands/app-fonts.ts +3 -2
  4. package/cli/commands/assert.ts +8 -7
  5. package/cli/commands/auth.ts +2 -1
  6. package/cli/commands/box/checkout-plane.ts +109 -29
  7. package/cli/commands/box/cloud-box.ts +294 -0
  8. package/cli/commands/box/plane-share.ts +208 -0
  9. package/cli/commands/box/rnx-command.ts +2 -2
  10. package/cli/commands/box/search-command.ts +157 -0
  11. package/cli/commands/box.ts +269 -13
  12. package/cli/commands/config.ts +2 -1
  13. package/cli/commands/control.ts +43 -34
  14. package/cli/commands/daemon.ts +22 -21
  15. package/cli/commands/debug.ts +17 -15
  16. package/cli/commands/detox.ts +5 -4
  17. package/cli/commands/device.ts +6 -5
  18. package/cli/commands/electron.ts +6 -5
  19. package/cli/commands/flow.ts +12 -11
  20. package/cli/commands/inspect/find.ts +2 -1
  21. package/cli/commands/inspect/keyboard.ts +2 -1
  22. package/cli/commands/inspect/sleep.ts +2 -1
  23. package/cli/commands/inspect/wait-event.ts +4 -3
  24. package/cli/commands/inspect/wait-idle.ts +2 -1
  25. package/cli/commands/inspect/wait-ready.ts +3 -2
  26. package/cli/commands/inspect/wait-selector.ts +4 -3
  27. package/cli/commands/inspect/wait.ts +2 -1
  28. package/cli/commands/inspect.ts +82 -80
  29. package/cli/commands/install-desktop.ts +5 -4
  30. package/cli/commands/login.ts +3 -2
  31. package/cli/commands/mode.ts +2 -1
  32. package/cli/commands/record.ts +38 -37
  33. package/cli/commands/runtime.ts +9 -8
  34. package/cli/commands/screenshot-layers.ts +4 -3
  35. package/cli/commands/screenshot-mode.ts +2 -1
  36. package/cli/commands/screenshot.ts +8 -7
  37. package/cli/commands/screenshots-capture.ts +2 -1
  38. package/cli/commands/serve.ts +5 -4
  39. package/cli/commands/shell-boolean-mode.ts +2 -1
  40. package/cli/commands/skills.ts +11 -10
  41. package/cli/commands/telemetry.ts +2 -1
  42. package/cli/commands/three-mode.ts +2 -1
  43. package/cli/commands/timeline.ts +4 -3
  44. package/cli/commands/upload.ts +14 -13
  45. package/cli/commands/what-happened.ts +2 -1
  46. package/cli/outbound-endpoints.ts +37 -1
  47. package/cli/recording-access.ts +6 -5
  48. package/cli/run-rnx.ts +140 -0
  49. package/dist-cli/bin.js +6 -6
  50. package/dist-cli/chunks/{agent-OUUFWDLF.js → agent-W2D2V7WF.js} +2 -2
  51. package/dist-cli/chunks/{agent-wrapper-7BE7DR4R.js → agent-wrapper-GVNM2Z4S.js} +2 -2
  52. package/dist-cli/chunks/app-fonts-J6R4TTXD.js +4 -0
  53. package/dist-cli/chunks/{assert-I3CMRNS7.js → assert-YN6U4ZTB.js} +11 -11
  54. package/dist-cli/chunks/auth-XGYZAK2P.js +12 -0
  55. package/dist-cli/chunks/{beta-BHSKY4K4.js → beta-WRDMHHWJ.js} +2 -2
  56. package/dist-cli/chunks/{box-3CVWXVTT.js → box-ML5TFUQW.js} +617 -596
  57. package/dist-cli/chunks/{camera-MNK73TYV.js → camera-OBEI4FZA.js} +2 -2
  58. package/dist-cli/chunks/{chunk-JXARZBWS.js → chunk-2FIT64GR.js} +1 -1
  59. package/dist-cli/chunks/{chunk-RYGQ6LZN.js → chunk-2LH4DYPM.js} +2 -2
  60. package/dist-cli/chunks/chunk-2QW3JH6T.js +99 -0
  61. package/dist-cli/chunks/{chunk-2APXX4RN.js → chunk-2W4YBCPX.js} +2 -2
  62. package/dist-cli/chunks/{chunk-X3PEDNLV.js → chunk-42YOIDLR.js} +2 -2
  63. package/dist-cli/chunks/{chunk-6BMM6OCW.js → chunk-47MSXLWK.js} +2 -2
  64. package/dist-cli/chunks/chunk-4OLUFHEZ.js +74 -0
  65. package/dist-cli/chunks/{chunk-WUR4FXWT.js → chunk-5PPVVGLH.js} +25 -25
  66. package/dist-cli/chunks/{chunk-AKJYEX54.js → chunk-6AZKZ5AJ.js} +2 -2
  67. package/dist-cli/chunks/{chunk-2PVVIPQS.js → chunk-6FPXLSBJ.js} +1 -1
  68. package/dist-cli/chunks/{chunk-CCI3DZKE.js → chunk-763GBI7G.js} +2 -2
  69. package/dist-cli/chunks/{chunk-CZ2HCNRK.js → chunk-7ASCDKA3.js} +1 -1
  70. package/dist-cli/chunks/{chunk-ZNNE5MAV.js → chunk-7Q4WHJDH.js} +1 -1
  71. package/dist-cli/chunks/{chunk-IYPMULI7.js → chunk-7TWD4P6J.js} +2 -2
  72. package/dist-cli/chunks/{chunk-45X3D6CI.js → chunk-7YB46LGI.js} +1 -1
  73. package/dist-cli/chunks/{chunk-3454VMHD.js → chunk-ADJSGO55.js} +2 -2
  74. package/dist-cli/chunks/{chunk-F4OTMARL.js → chunk-AGZZWLEL.js} +2 -2
  75. package/dist-cli/chunks/{chunk-UDXQCEJU.js → chunk-AVIH3TAZ.js} +11 -11
  76. package/dist-cli/chunks/{chunk-DCPDMF27.js → chunk-B26ZJUQA.js} +2 -2
  77. package/dist-cli/chunks/{chunk-D6MGELKZ.js → chunk-BNS5LNEU.js} +1 -1
  78. package/dist-cli/chunks/{chunk-FHRG772G.js → chunk-CJQXP4XM.js} +1 -1
  79. package/dist-cli/chunks/{chunk-OROUEG22.js → chunk-DP6RGTNN.js} +2 -2
  80. package/dist-cli/chunks/{chunk-V7I4CTAX.js → chunk-DYFOLOYL.js} +2 -2
  81. package/dist-cli/chunks/chunk-ERBGQEJ5.js +4 -0
  82. package/dist-cli/chunks/{chunk-H2GB2PLY.js → chunk-ESWSXF55.js} +2 -2
  83. package/dist-cli/chunks/{chunk-FGGESBWD.js → chunk-FLNGSIBL.js} +2 -2
  84. package/dist-cli/chunks/{chunk-4SCVGBOX.js → chunk-GQGS7TRK.js} +1 -1
  85. package/dist-cli/chunks/{chunk-WGE2P25F.js → chunk-H4DU457S.js} +1 -1
  86. package/dist-cli/chunks/{chunk-TBK5UXW2.js → chunk-HFY35VAG.js} +2 -2
  87. package/dist-cli/chunks/{chunk-JLOOLA47.js → chunk-HV3WOWQM.js} +1 -1
  88. package/dist-cli/chunks/{chunk-RV76U3EI.js → chunk-HZ365EMS.js} +10 -10
  89. package/dist-cli/chunks/chunk-IPNDXGVA.js +11 -0
  90. package/dist-cli/chunks/{chunk-TAC4CEIX.js → chunk-IX7Z6ZVJ.js} +2 -2
  91. package/dist-cli/chunks/{chunk-UQJY6O72.js → chunk-IYU2EOIU.js} +1 -1
  92. package/dist-cli/chunks/chunk-J6A5W2DI.js +30 -0
  93. package/dist-cli/chunks/chunk-JKRMBNPO.js +28 -0
  94. package/dist-cli/chunks/{chunk-VGJLCFSC.js → chunk-JUTLQ3GX.js} +1 -1
  95. package/dist-cli/chunks/{chunk-5QFJFASW.js → chunk-KHGXEU4L.js} +1 -1
  96. package/dist-cli/chunks/{chunk-A56GQVNF.js → chunk-KL34TRNW.js} +1 -1
  97. package/dist-cli/chunks/chunk-LFANRLWK.js +7 -0
  98. package/dist-cli/chunks/{chunk-NPVEBXU3.js → chunk-MOIQZVRU.js} +2 -2
  99. package/dist-cli/chunks/chunk-MW4E5EED.js +497 -0
  100. package/dist-cli/chunks/{chunk-AJJCYLBC.js → chunk-N3LAMTGS.js} +2 -2
  101. package/dist-cli/chunks/{chunk-QE5MC4V3.js → chunk-NQ4CI2VI.js} +1 -1
  102. package/dist-cli/chunks/{chunk-6F3WOWFC.js → chunk-OF3U4SZO.js} +2 -2
  103. package/dist-cli/chunks/{chunk-DKSZZABN.js → chunk-OF5OBB67.js} +1 -1
  104. package/dist-cli/chunks/{chunk-MXWME7ET.js → chunk-OQPZOEEP.js} +1 -1
  105. package/dist-cli/chunks/{chunk-SSBZA5VZ.js → chunk-OWDJQWJ7.js} +1 -1
  106. package/dist-cli/chunks/{chunk-YLNSLMQM.js → chunk-PTWMX4AA.js} +2 -2
  107. package/dist-cli/chunks/{chunk-TTUZ5MCK.js → chunk-RYJTPZCX.js} +1 -1
  108. package/dist-cli/chunks/{chunk-A72MIIJI.js → chunk-S3AH2O67.js} +1 -1
  109. package/dist-cli/chunks/{chunk-ZW6JXEVX.js → chunk-SH2HXHR2.js} +2 -2
  110. package/dist-cli/chunks/{chunk-EKAEEWC2.js → chunk-SHLJYXIN.js} +1 -1
  111. package/dist-cli/chunks/{chunk-4G4VCT2M.js → chunk-TL22EBF5.js} +2 -2
  112. package/dist-cli/chunks/{chunk-LU3AHE6T.js → chunk-TWNLRDIH.js} +2 -2
  113. package/dist-cli/chunks/{chunk-B5NW4TT3.js → chunk-UHYAY3RU.js} +2 -2
  114. package/dist-cli/chunks/{chunk-N5KDDGV6.js → chunk-UPMR6Y5P.js} +2 -2
  115. package/dist-cli/chunks/chunk-VEFSHPSU.js +24 -0
  116. package/dist-cli/chunks/{chunk-YPBDPVLR.js → chunk-WCB5XPCH.js} +2 -2
  117. package/dist-cli/chunks/{chunk-USHV3QFZ.js → chunk-WHZE63R2.js} +2 -2
  118. package/dist-cli/chunks/{chunk-ZEF57HQO.js → chunk-WLRIPAGJ.js} +1 -1
  119. package/dist-cli/chunks/{chunk-FBMNYSSW.js → chunk-Y7SKGRAV.js} +1 -1
  120. package/dist-cli/chunks/chunk-Z6CQDU74.js +4 -0
  121. package/dist-cli/chunks/{cleanup-H4XFRMZV.js → cleanup-PTJBMC34.js} +2 -2
  122. package/dist-cli/chunks/cli-version-6ZBD6GKP.js +4 -0
  123. package/dist-cli/chunks/{compat-NKGYERLN.js → compat-FJ2HWRAG.js} +2 -2
  124. package/dist-cli/chunks/config-PYGH7JPN.js +13 -0
  125. package/dist-cli/chunks/{connect-WWWAVVCF.js → connect-AJV4PZA4.js} +2 -2
  126. package/dist-cli/chunks/control-667PAYX4.js +4 -0
  127. package/dist-cli/chunks/daemon-EIBOHZYQ.js +4 -0
  128. package/dist-cli/chunks/{debug-RO6GLDIM.js → debug-XF2XUDHN.js} +20 -20
  129. package/dist-cli/chunks/desktop-HEZYNFYW.js +31 -0
  130. package/dist-cli/chunks/detox-JCFZJRNE.js +52 -0
  131. package/dist-cli/chunks/device-KGUVZHFW.js +18 -0
  132. package/dist-cli/chunks/{diagnose-HDRTUPDO.js → diagnose-KVJQ3TOI.js} +2 -2
  133. package/dist-cli/chunks/{disk-cleanup-QP2NYN43.js → disk-cleanup-7HQXJFUX.js} +2 -2
  134. package/dist-cli/chunks/{do-chain-6LP6SV5J.js → do-chain-3FYJNA2N.js} +2 -2
  135. package/dist-cli/chunks/drivers-5S6M5TJB.js +4 -0
  136. package/dist-cli/chunks/{env-4XD5D2G6.js → env-BX6S2SOM.js} +2 -2
  137. package/dist-cli/chunks/{film-I5SIXLS5.js → film-2WZUVYFA.js} +3 -3
  138. package/dist-cli/chunks/flow-6BZSWNHG.js +4 -0
  139. package/dist-cli/chunks/help-OGJBD4NP.js +4 -0
  140. package/dist-cli/chunks/{hidden-runtime-alias-LJV2S4M2.js → hidden-runtime-alias-PGF55OFH.js} +2 -2
  141. package/dist-cli/chunks/{home-paths-RFI2QRC3.js → home-paths-EGRMTSGE.js} +2 -2
  142. package/dist-cli/chunks/inspect-MZVDGAN4.js +4 -0
  143. package/dist-cli/chunks/install-desktop-QB76DNZF.js +4 -0
  144. package/dist-cli/chunks/login-HRFPYPZ3.js +26 -0
  145. package/dist-cli/chunks/{logout-DKXZZ6HU.js → logout-LRH5HZGP.js} +2 -2
  146. package/dist-cli/chunks/{maestro-WKGVVWRA.js → maestro-S53T4PPE.js} +3 -3
  147. package/dist-cli/chunks/{maestro-generate-KCK6RK2C.js → maestro-generate-YKIVKOB3.js} +3 -3
  148. package/dist-cli/chunks/mode-47JKNUNS.js +26 -0
  149. package/dist-cli/chunks/{optional-demo-registry-EUSKBIPP.js → optional-demo-registry-ARZSPS5Y.js} +2 -2
  150. package/dist-cli/chunks/{perf-IMZMA5MT.js → perf-J73OKB4T.js} +2 -2
  151. package/dist-cli/chunks/{permissions-SLWXTLH6.js → permissions-L3FPI56H.js} +2 -2
  152. package/dist-cli/chunks/{platform-HQ735LAE.js → platform-YAROAHRM.js} +3 -3
  153. package/dist-cli/chunks/privacy-JZQCXHKN.js +4 -0
  154. package/dist-cli/chunks/record-QAQMGYOO.js +72 -0
  155. package/dist-cli/chunks/report-issue-4FVZCAFQ.js +29 -0
  156. package/dist-cli/chunks/reset-NYFUFZCG.js +4 -0
  157. package/dist-cli/chunks/resolve-target-SP22ZGFZ.js +4 -0
  158. package/dist-cli/chunks/runtime-WTS2QMXZ.js +4 -0
  159. package/dist-cli/chunks/{screenshot-command-HAJB5FHK.js → screenshot-command-Q5MGGEWY.js} +11 -11
  160. package/dist-cli/chunks/screenshot-layers-4JW7DRL7.js +20 -0
  161. package/dist-cli/chunks/screenshots-capture-X5PMO44E.js +42 -0
  162. package/dist-cli/chunks/{serve-TM46TKFW.js → serve-3JWJ45J3.js} +20 -20
  163. package/dist-cli/chunks/setup-USPDJESI.js +4 -0
  164. package/dist-cli/chunks/{sim-client-LQUZSO45.js → sim-client-RL6DGOJE.js} +2 -2
  165. package/dist-cli/chunks/skills-QNO6GDJB.js +50 -0
  166. package/dist-cli/chunks/{state-RUVM4Z4S.js → state-OGUCSUC3.js} +2 -2
  167. package/dist-cli/chunks/{storage-GQ33UTYN.js → storage-TG7KFQZ3.js} +2 -2
  168. package/dist-cli/chunks/store-ZYMQW2LN.js +4 -0
  169. package/dist-cli/chunks/telemetry-7CSLUUIW.js +4 -0
  170. package/dist-cli/chunks/timeline-R4G5P335.js +24 -0
  171. package/dist-cli/chunks/{upgrade-AZDZLUIT.js → upgrade-RDTXJ6O3.js} +2 -2
  172. package/dist-cli/chunks/upload-PLF2DPMS.js +4 -0
  173. package/dist-cli/chunks/{version-N3ALB75G.js → version-GU2NUZDS.js} +2 -2
  174. package/dist-cli/chunks/{web-XPHFZATA.js → web-7PUVG3GE.js} +2 -2
  175. package/dist-cli/chunks/welcome-4GGUV7FY.js +5 -0
  176. package/dist-cli/chunks/what-happened-OHAAX6F6.js +25 -0
  177. package/dist-cli/chunks/{ws-bridge-RUXOYOU6.js → ws-bridge-Z3O35MNF.js} +2 -2
  178. package/dist-lib/agent-daemon-client.cjs +1 -1
  179. package/dist-lib/agent-events.cjs +1 -1
  180. package/dist-lib/agent-identity.cjs +1 -1
  181. package/dist-lib/agent-sessions.cjs +1 -1
  182. package/dist-lib/attached-projects.cjs +1 -1
  183. package/dist-lib/auth/shared-session.cjs +1 -1
  184. package/dist-lib/backend-origin.cjs +1 -1
  185. package/dist-lib/beta.cjs +1 -1
  186. package/dist-lib/beta.mjs +1 -1
  187. package/dist-lib/bridge-constants.cjs +1 -1
  188. package/dist-lib/bridge-contract-input.cjs +1 -1
  189. package/dist-lib/bridge-contract-input.mjs +1 -1
  190. package/dist-lib/bridge-contract.cjs +1 -1
  191. package/dist-lib/bridge-contract.mjs +1 -1
  192. package/dist-lib/cli-constants.cjs +1 -1
  193. package/dist-lib/config.cjs +1 -1
  194. package/dist-lib/detox/index.cjs +1 -1
  195. package/dist-lib/dev-bundle-resolution.cjs +69 -34
  196. package/dist-lib/home-paths.cjs +1 -1
  197. package/dist-lib/host/bridge-host.cjs +430 -106
  198. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  199. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  200. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  201. package/dist-lib/host/replacement-module-handler.cjs +1 -1
  202. package/dist-lib/host/websocket-proxy.cjs +1 -1
  203. package/dist-lib/index.cjs +1 -1
  204. package/dist-lib/jump-to-source-babel.cjs +1 -1
  205. package/dist-lib/menu.cjs +1 -1
  206. package/dist-lib/menu.mjs +1 -1
  207. package/dist-lib/metro-production-bundle.cjs +1 -1
  208. package/dist-lib/metro-production-bundle.mjs +1 -1
  209. package/dist-lib/metro.cjs +1 -1
  210. package/dist-lib/profiles.cjs +1 -1
  211. package/dist-lib/public-brand.cjs +1 -1
  212. package/dist-lib/react-native-host-modules.cjs +2 -1
  213. package/dist-lib/react-native-host-modules.mjs +2 -1
  214. package/dist-lib/render-mode.cjs +1 -1
  215. package/dist-lib/scripts/dev-server-scanner.cjs +149 -93
  216. package/dist-lib/sdk.cjs +1 -1
  217. package/dist-lib/sdk.mjs +1 -1
  218. package/dist-lib/skills.cjs +757 -353
  219. package/dist-lib/vite.cjs +1 -1
  220. package/package.json +1 -1
  221. package/scripts/dev-server-scanner.ts +275 -92
  222. package/src/dev-bundle-resolution.ts +123 -40
  223. package/src/host/bridge-host.ts +11 -0
  224. package/src/host/plane-host.ts +203 -0
  225. package/src/react-native-host-modules.ts +1 -0
  226. package/dist-cli/chunks/app-fonts-66V7TL26.js +0 -4
  227. package/dist-cli/chunks/auth-3FMWUVJH.js +0 -12
  228. package/dist-cli/chunks/chunk-3NLB5LCN.js +0 -28
  229. package/dist-cli/chunks/chunk-5RWUFIZW.js +0 -24
  230. package/dist-cli/chunks/chunk-COXCR6UU.js +0 -7
  231. package/dist-cli/chunks/chunk-JH5KE2OI.js +0 -4
  232. package/dist-cli/chunks/chunk-ROISJKH3.js +0 -99
  233. package/dist-cli/chunks/chunk-SUHAD6N2.js +0 -30
  234. package/dist-cli/chunks/chunk-TIB3CTRX.js +0 -497
  235. package/dist-cli/chunks/chunk-YFZLDFZK.js +0 -74
  236. package/dist-cli/chunks/cli-version-MJWLDXBW.js +0 -4
  237. package/dist-cli/chunks/config-WK7467KI.js +0 -13
  238. package/dist-cli/chunks/control-ESI7IZNN.js +0 -4
  239. package/dist-cli/chunks/daemon-UCLO27YP.js +0 -4
  240. package/dist-cli/chunks/desktop-FF34Z5X4.js +0 -31
  241. package/dist-cli/chunks/detox-Q3CWY5UX.js +0 -52
  242. package/dist-cli/chunks/device-WN4ZWLES.js +0 -18
  243. package/dist-cli/chunks/drivers-OR2JPAXD.js +0 -4
  244. package/dist-cli/chunks/flow-4GWW5DYT.js +0 -4
  245. package/dist-cli/chunks/help-MIIWOT3C.js +0 -4
  246. package/dist-cli/chunks/inspect-3ZGNJENJ.js +0 -4
  247. package/dist-cli/chunks/install-desktop-BYOJVTLS.js +0 -4
  248. package/dist-cli/chunks/login-GNIFSAFE.js +0 -26
  249. package/dist-cli/chunks/mode-YVMX7OJY.js +0 -26
  250. package/dist-cli/chunks/privacy-EDC7PF7G.js +0 -4
  251. package/dist-cli/chunks/record-N4KTTVDI.js +0 -72
  252. package/dist-cli/chunks/report-issue-PP752HAD.js +0 -29
  253. package/dist-cli/chunks/reset-DRMKGVP4.js +0 -4
  254. package/dist-cli/chunks/resolve-target-7WWI32R3.js +0 -4
  255. package/dist-cli/chunks/runtime-EKJV2ZVI.js +0 -4
  256. package/dist-cli/chunks/screenshot-layers-GOCJVM2G.js +0 -20
  257. package/dist-cli/chunks/screenshots-capture-YYJLYA3V.js +0 -42
  258. package/dist-cli/chunks/setup-FVPVIYE4.js +0 -4
  259. package/dist-cli/chunks/skills-V5TE57FN.js +0 -50
  260. package/dist-cli/chunks/store-IKNBDDKM.js +0 -4
  261. package/dist-cli/chunks/telemetry-2ZVU6WLK.js +0 -4
  262. package/dist-cli/chunks/timeline-LP7DMJYT.js +0 -24
  263. package/dist-cli/chunks/upload-DFQK2Q7X.js +0 -4
  264. package/dist-cli/chunks/welcome-2NFYLWLJ.js +0 -5
  265. package/dist-cli/chunks/what-happened-EJ5PHKT7.js +0 -25
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.369 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.371 | (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;
@@ -14659,9 +14659,9 @@ function safeChildPlistName(value) {
14659
14659
  name: file.toLowerCase().endsWith(".plist") ? file : `${file}.plist`
14660
14660
  };
14661
14661
  }
14662
- async function readableSettingsBundle(path18) {
14662
+ async function readableSettingsBundle(path20) {
14663
14663
  try {
14664
- const root = await (0, import_promises.stat)((0, import_node_path.join)(path18, "Root.plist"));
14664
+ const root = await (0, import_promises.stat)((0, import_node_path.join)(path20, "Root.plist"));
14665
14665
  return root.isFile() && root.size <= MAX_PLIST_BYTES;
14666
14666
  } catch {
14667
14667
  return false;
@@ -14706,11 +14706,11 @@ async function settingsBundleFingerprint(bundlePath) {
14706
14706
  return null;
14707
14707
  }
14708
14708
  }
14709
- async function parsePlistRecord(path18) {
14709
+ async function parsePlistRecord(path20) {
14710
14710
  try {
14711
- const metadata = await (0, import_promises.stat)(path18);
14711
+ const metadata = await (0, import_promises.stat)(path20);
14712
14712
  if (!metadata.isFile() || metadata.size > MAX_PLIST_BYTES) return null;
14713
- const source = await (0, import_promises.readFile)(path18, "utf8");
14713
+ const source = await (0, import_promises.readFile)(path20, "utf8");
14714
14714
  const parsed = parsePlistSource(source);
14715
14715
  return isRecord2(parsed) ? parsed : null;
14716
14716
  } catch {
@@ -14972,6 +14972,25 @@ var init_optional_demo_registry = __esm({
14972
14972
  });
14973
14973
 
14974
14974
  // scripts/dev-server-scanner.ts
14975
+ function execGuarded(command, timeout, extra = {}) {
14976
+ let child;
14977
+ const ran = new Promise((resolve7, reject) => {
14978
+ child = (0, import_child_process.exec)(command, { encoding: "utf8", timeout, ...extra }, (err, stdout) => {
14979
+ if (err) reject(err);
14980
+ else resolve7({ stdout });
14981
+ });
14982
+ });
14983
+ const deadline = new Promise((_resolve, reject) => {
14984
+ const timer = setTimeout(() => {
14985
+ child?.kill("SIGKILL");
14986
+ const err = new Error(`\`${command}\` did not exit within ${timeout}ms`);
14987
+ Object.assign(err, { killed: true });
14988
+ reject(err);
14989
+ }, timeout + EXEC_EXIT_GRACE_MS);
14990
+ timer.unref?.();
14991
+ });
14992
+ return Promise.race([ran, deadline]);
14993
+ }
14975
14994
  async function tcpPing(port, timeout = TCP_GATE_MS) {
14976
14995
  try {
14977
14996
  return await Promise.any(
@@ -14998,93 +15017,116 @@ async function tcpPing(port, timeout = TCP_GATE_MS) {
14998
15017
  return null;
14999
15018
  }
15000
15019
  }
15001
- function httpGet(hostname, port, path18, method = "GET", timeout = TIMEOUT_MS, headers = {}) {
15020
+ function httpGet(hostname, port, path20, method = "GET", timeout = TIMEOUT_MS, headers = {}) {
15002
15021
  return new Promise((resolve7) => {
15003
- const req = import_http.default.request(
15004
- { hostname, port, path: path18, method, timeout, headers },
15005
- (res) => {
15006
- let body = "";
15007
- res.on("data", (c) => body += c.toString());
15008
- const contentType = (() => {
15009
- const raw = res.headers["content-type"];
15010
- if (typeof raw === "string") return raw;
15011
- if (Array.isArray(raw)) return raw[0];
15012
- return void 0;
15013
- })();
15014
- res.on(
15015
- "end",
15016
- () => resolve7({
15017
- statusCode: res.statusCode || 0,
15018
- body,
15019
- contentType,
15020
- hostname
15021
- })
15022
- );
15023
- }
15024
- );
15025
- req.on("error", () => resolve7(null));
15026
- req.setTimeout(timeout, () => {
15027
- req.destroy();
15028
- resolve7(null);
15022
+ let settled = false;
15023
+ const settle = (value) => {
15024
+ if (settled) return;
15025
+ settled = true;
15026
+ resolve7(value);
15027
+ };
15028
+ const req = import_http.default.request({ hostname, port, path: path20, method, headers }, (res) => {
15029
+ let body = "";
15030
+ res.on("data", (c) => body += c.toString());
15031
+ const contentType = (() => {
15032
+ const raw = res.headers["content-type"];
15033
+ if (typeof raw === "string") return raw;
15034
+ if (Array.isArray(raw)) return raw[0];
15035
+ return void 0;
15036
+ })();
15037
+ res.on(
15038
+ "end",
15039
+ () => settle({
15040
+ statusCode: res.statusCode || 0,
15041
+ body,
15042
+ contentType,
15043
+ hostname
15044
+ })
15045
+ );
15029
15046
  });
15047
+ req.on("error", () => settle(null));
15030
15048
  req.end();
15049
+ const giveUp = setTimeout(() => {
15050
+ settle(null);
15051
+ req.socket?.unref();
15052
+ }, timeout);
15053
+ const abandon = setTimeout(() => req.destroy(), BODY_TIMEOUT_MS);
15054
+ abandon.unref?.();
15055
+ req.on("close", () => {
15056
+ clearTimeout(giveUp);
15057
+ clearTimeout(abandon);
15058
+ setImmediate(() => settle(null));
15059
+ });
15031
15060
  });
15032
15061
  }
15062
+ function isOwnShellPort(port) {
15063
+ const offset = Number(process.env.PORT_OFFSET) || 0;
15064
+ if (port >= SELF_PORT_RANGE_START && port <= SELF_PORT_RANGE_END) return true;
15065
+ return offset > 0 && port >= SELF_PORT_RANGE_START + offset && port <= SELF_PORT_RANGE_END + offset;
15066
+ }
15033
15067
  function acceptPort(port, excluded) {
15034
15068
  if (port <= 0 || port >= 2e4) return false;
15035
15069
  if (excluded.has(port)) return false;
15036
- if (port >= 5170 && port <= 5200) return false;
15070
+ if (isOwnShellPort(port)) return false;
15037
15071
  return true;
15038
15072
  }
15039
- async function discoverListeningProcesses(excludePorts = []) {
15073
+ function wasKilledByTimeout(err) {
15074
+ return !!(err && typeof err === "object" && err.killed);
15075
+ }
15076
+ async function scanListeningProcesses(excludePorts = []) {
15040
15077
  const excluded = new Set(excludePorts);
15078
+ let timedOut = false;
15041
15079
  try {
15042
- const { stdout } = await execP(
15080
+ const { stdout } = await execGuarded(
15043
15081
  `lsof -iTCP -sTCP:LISTEN -P -n 2>/dev/null | grep -E '^(node|bun)'`,
15044
- { encoding: "utf8", timeout: 2e3 }
15082
+ LISTING_TIMEOUT_MS
15045
15083
  );
15046
- if (stdout.trim()) {
15047
- const seen = /* @__PURE__ */ new Map();
15048
- for (const line of stdout.trim().split("\n")) {
15049
- const parts = line.trim().split(/\s+/);
15050
- if (parts.length < 9) continue;
15051
- const pid = Number(parts[1]);
15052
- const addr = parts[8];
15053
- const m = addr.match(/:(\d+)$/);
15054
- if (!m) continue;
15055
- const port = Number(m[1]);
15056
- if (!acceptPort(port, excluded)) continue;
15057
- if (!seen.has(port)) seen.set(port, pid);
15058
- }
15059
- if (seen.size > 0) {
15060
- return [...seen.entries()].map(([port, pid]) => ({ port, pid }));
15061
- }
15084
+ const seen = /* @__PURE__ */ new Map();
15085
+ for (const line of stdout.trim() ? stdout.trim().split("\n") : []) {
15086
+ const parts = line.trim().split(/\s+/);
15087
+ if (parts.length < 9) continue;
15088
+ const pid = Number(parts[1]);
15089
+ const addr = parts[8];
15090
+ const m = addr.match(/:(\d+)$/);
15091
+ if (!m) continue;
15092
+ const port = Number(m[1]);
15093
+ if (!acceptPort(port, excluded)) continue;
15094
+ if (!seen.has(port)) seen.set(port, pid);
15062
15095
  }
15063
- } catch {
15096
+ return {
15097
+ state: "looked",
15098
+ processes: [...seen.entries()].map(([port, pid]) => ({ port, pid }))
15099
+ };
15100
+ } catch (err) {
15101
+ if (wasKilledByTimeout(err)) timedOut = true;
15064
15102
  }
15065
15103
  try {
15066
- const { stdout } = await execP(`ss -tlnp 2>/dev/null | grep -E '"(node|bun)"'`, {
15067
- encoding: "utf8",
15068
- timeout: 2e3
15069
- });
15070
- if (stdout.trim()) {
15071
- const seen = /* @__PURE__ */ new Map();
15072
- for (const line of stdout.trim().split("\n")) {
15073
- const portMatch = line.match(/:(\d+)\s/);
15074
- const pidMatch = line.match(/pid=(\d+)/);
15075
- if (!portMatch) continue;
15076
- const port = Number(portMatch[1]);
15077
- const pid = pidMatch ? Number(pidMatch[1]) : 0;
15078
- if (!acceptPort(port, excluded)) continue;
15079
- if (!seen.has(port)) seen.set(port, pid);
15080
- }
15081
- if (seen.size > 0) {
15082
- return [...seen.entries()].map(([port, pid]) => ({ port, pid }));
15083
- }
15104
+ const { stdout } = await execGuarded(
15105
+ `ss -tlnp 2>/dev/null | grep -E '"(node|bun)"'`,
15106
+ LISTING_TIMEOUT_MS
15107
+ );
15108
+ const seen = /* @__PURE__ */ new Map();
15109
+ for (const line of stdout.trim() ? stdout.trim().split("\n") : []) {
15110
+ const portMatch = line.match(/:(\d+)\s/);
15111
+ const pidMatch = line.match(/pid=(\d+)/);
15112
+ if (!portMatch) continue;
15113
+ const port = Number(portMatch[1]);
15114
+ const pid = pidMatch ? Number(pidMatch[1]) : 0;
15115
+ if (!acceptPort(port, excluded)) continue;
15116
+ if (!seen.has(port)) seen.set(port, pid);
15084
15117
  }
15085
- } catch {
15118
+ return {
15119
+ state: "looked",
15120
+ processes: [...seen.entries()].map(([port, pid]) => ({ port, pid }))
15121
+ };
15122
+ } catch (err) {
15123
+ if (wasKilledByTimeout(err)) timedOut = true;
15086
15124
  }
15087
- return FALLBACK_PORTS.filter((p) => acceptPort(p.port, excluded));
15125
+ if (timedOut) return { state: "could-not-look" };
15126
+ return {
15127
+ state: "looked",
15128
+ processes: FALLBACK_PORTS.filter((p) => acceptPort(p.port, excluded))
15129
+ };
15088
15130
  }
15089
15131
  function pidFromNetstat(stdout, port) {
15090
15132
  for (const line of stdout.split("\n")) {
@@ -15104,9 +15146,7 @@ function pidFromNetstat(stdout, port) {
15104
15146
  async function resolveListeningPid(port) {
15105
15147
  if (!Number.isInteger(port) || port <= 0) return null;
15106
15148
  try {
15107
- const { stdout } = await execP("netstat -anv -p tcp 2>/dev/null", {
15108
- encoding: "utf8",
15109
- timeout: 8e3,
15149
+ const { stdout } = await execGuarded("netstat -anv -p tcp 2>/dev/null", 8e3, {
15110
15150
  maxBuffer: 16 * 1024 * 1024
15111
15151
  });
15112
15152
  const pid = pidFromNetstat(stdout, port);
@@ -15114,9 +15154,7 @@ async function resolveListeningPid(port) {
15114
15154
  } catch {
15115
15155
  }
15116
15156
  try {
15117
- const { stdout } = await execP(`ss -tlnp 2>/dev/null`, {
15118
- encoding: "utf8",
15119
- timeout: 8e3,
15157
+ const { stdout } = await execGuarded(`ss -tlnp 2>/dev/null`, 8e3, {
15120
15158
  maxBuffer: 16 * 1024 * 1024
15121
15159
  });
15122
15160
  for (const line of stdout.split("\n")) {
@@ -15141,10 +15179,10 @@ async function resolveProcessCwd(pid) {
15141
15179
  } catch {
15142
15180
  }
15143
15181
  try {
15144
- const { stdout } = await execP(`lsof -b -w -p ${pid} -a -d cwd -Fn 2>/dev/null`, {
15145
- encoding: "utf8",
15146
- timeout: 8e3
15147
- });
15182
+ const { stdout } = await execGuarded(
15183
+ `lsof -b -w -p ${pid} -a -d cwd -Fn 2>/dev/null`,
15184
+ 8e3
15185
+ );
15148
15186
  for (const line of stdout.split("\n")) {
15149
15187
  if (line.startsWith("n") && line.length > 1) {
15150
15188
  const cwd = line.slice(1).trim();
@@ -15306,7 +15344,8 @@ async function probePort(port, buildIconProxyUrl, listeningProcess) {
15306
15344
  const manifestParsed = manifestRes ? safeParseManifest(manifestRes.body) : null;
15307
15345
  const manifestLaunchUrl = typeof manifestParsed?.launchAsset?.url === "string" ? manifestParsed.launchAsset.url : null;
15308
15346
  const manifestClient = manifestParsed?.extra?.expoClient || manifestParsed?.extra || {};
15309
- if (manifestParsed && (manifestLaunchUrl || typeof manifestClient.name === "string")) {
15347
+ const manifestState = !manifestRes ? "no-answer" : manifestParsed && (manifestLaunchUrl || typeof manifestClient.name === "string") ? "resolved" : "no-manifest";
15348
+ if (manifestState === "resolved") {
15310
15349
  knownNonPatched.add(port);
15311
15350
  const launchUrl2 = manifestLaunchUrl || `${buildLoopbackProbeBaseUrl("http:", probeHostname, port)}/index.bundle?platform=ios&dev=true&hot=true&minify=false`;
15312
15351
  const framework = launchUrl2.includes(
@@ -15327,17 +15366,15 @@ async function probePort(port, buildIconProxyUrl, listeningProcess) {
15327
15366
  }
15328
15367
  if (statusRes && statusRes.body.includes("packager-status:running")) {
15329
15368
  knownNonPatched.add(port);
15369
+ const result = makeResult(
15370
+ probeHostname,
15371
+ port,
15372
+ expoRes && expoRes.statusCode === 200 ? "expo" : "metro",
15373
+ owner
15374
+ );
15375
+ if (manifestState === "no-answer") result.bundleUrlProvisional = true;
15330
15376
  return enrichWithManifest(
15331
- applyManifest(
15332
- makeResult(
15333
- probeHostname,
15334
- port,
15335
- expoRes && expoRes.statusCode === 200 ? "expo" : "metro",
15336
- owner
15337
- ),
15338
- manifestRes,
15339
- buildIconProxyUrl
15340
- ),
15377
+ applyManifest(result, manifestRes, buildIconProxyUrl),
15341
15378
  buildIconProxyUrl
15342
15379
  );
15343
15380
  }
@@ -15430,7 +15467,9 @@ async function attachAppSettingsBundle(result, projectRoot) {
15430
15467
  }
15431
15468
  async function scanDevServers(opts = {}) {
15432
15469
  await loadOptionalDemoApps();
15433
- const processes = await discoverListeningProcesses(opts.excludePorts);
15470
+ const scan = await scanListeningProcesses(opts.excludePorts);
15471
+ if (scan.state === "could-not-look") throw new DevServerScanUnavailableError();
15472
+ const processes = scan.processes;
15434
15473
  const currentPorts = new Set(processes.map((p) => p.port));
15435
15474
  for (const p of [...portCache.keys()]) {
15436
15475
  if (!currentPorts.has(p)) portCache.delete(p);
@@ -15494,7 +15533,7 @@ async function scanDevServers(opts = {}) {
15494
15533
  }
15495
15534
  return results.filter((r) => !isSootSimSelfServer(r));
15496
15535
  }
15497
- var import_child_process, import_promises2, import_http, import_net, import_node_path3, import_util, execP, TIMEOUT_MS, MANIFEST_TIMEOUT_MS, TCP_GATE_MS, FALLBACK_PORTS, cwdByPid, ENRICH_TIMEOUT_MS, enrichmentCache, enrichmentInFlight, knownNonPatched, knownNonExpo, knownOne, portCache, NEGATIVE_CACHE_TTL_MS, WEAK_RESULT_CACHE_TTL_MS;
15536
+ var import_child_process, import_promises2, import_http, import_net, import_node_path3, EXEC_EXIT_GRACE_MS, TIMEOUT_MS, MANIFEST_TIMEOUT_MS, TCP_GATE_MS, BODY_TIMEOUT_MS, FALLBACK_PORTS, SELF_PORT_RANGE_START, SELF_PORT_RANGE_END, DevServerScanUnavailableError, LISTING_TIMEOUT_MS, cwdByPid, ENRICH_TIMEOUT_MS, enrichmentCache, enrichmentInFlight, knownNonPatched, knownNonExpo, knownOne, portCache, NEGATIVE_CACHE_TTL_MS, WEAK_RESULT_CACHE_TTL_MS;
15498
15537
  var init_dev_server_scanner = __esm({
15499
15538
  "scripts/dev-server-scanner.ts"() {
15500
15539
  "use strict";
@@ -15503,16 +15542,16 @@ var init_dev_server_scanner = __esm({
15503
15542
  import_http = __toESM(require("http"), 1);
15504
15543
  import_net = __toESM(require("net"), 1);
15505
15544
  import_node_path3 = require("node:path");
15506
- import_util = require("util");
15507
15545
  init_backend_origin();
15508
15546
  init_config();
15509
15547
  init_native_dev_bundle_url();
15510
15548
  init_settings_bundle();
15511
15549
  init_optional_demo_registry();
15512
- execP = (0, import_util.promisify)(import_child_process.exec);
15550
+ EXEC_EXIT_GRACE_MS = 2e3;
15513
15551
  TIMEOUT_MS = 250;
15514
15552
  MANIFEST_TIMEOUT_MS = 1500;
15515
15553
  TCP_GATE_MS = 120;
15554
+ BODY_TIMEOUT_MS = 3e4;
15516
15555
  FALLBACK_PORTS = [
15517
15556
  8081,
15518
15557
  8082,
@@ -15524,6 +15563,15 @@ var init_dev_server_scanner = __esm({
15524
15563
  3001,
15525
15564
  19e3
15526
15565
  ].map((port) => ({ port, pid: 0 }));
15566
+ SELF_PORT_RANGE_START = 5170;
15567
+ SELF_PORT_RANGE_END = 5200;
15568
+ DevServerScanUnavailableError = class extends Error {
15569
+ constructor() {
15570
+ super("could not enumerate listening processes (lsof/ss unavailable or timed out)");
15571
+ this.name = "DevServerScanUnavailableError";
15572
+ }
15573
+ };
15574
+ LISTING_TIMEOUT_MS = 1e4;
15527
15575
  cwdByPid = /* @__PURE__ */ new Map();
15528
15576
  ENRICH_TIMEOUT_MS = 2e4;
15529
15577
  enrichmentCache = /* @__PURE__ */ new Map();
@@ -18451,9 +18499,290 @@ var init_open_url = __esm({
18451
18499
  }
18452
18500
  });
18453
18501
 
18502
+ // cli/commands/box/checkout-plane.ts
18503
+ function looksBinary(filePath, size) {
18504
+ if (size === 0) return false;
18505
+ let fd;
18506
+ try {
18507
+ fd = import_node_fs9.default.openSync(filePath, "r");
18508
+ const buffer = Buffer.alloc(Math.min(BINARY_SNIFF_BYTES, size));
18509
+ const read = import_node_fs9.default.readSync(fd, buffer, 0, buffer.length, 0);
18510
+ return buffer.subarray(0, read).includes(0);
18511
+ } catch {
18512
+ return false;
18513
+ } finally {
18514
+ if (fd !== void 0) import_node_fs9.default.closeSync(fd);
18515
+ }
18516
+ }
18517
+ var import_node_child_process5, import_node_fs9, import_node_path10, CheckoutRequiresGitError, BINARY_SNIFF_BYTES, LIST_FILES_TIMEOUT_MS, CheckoutFilePlane;
18518
+ var init_checkout_plane = __esm({
18519
+ "cli/commands/box/checkout-plane.ts"() {
18520
+ "use strict";
18521
+ import_node_child_process5 = require("node:child_process");
18522
+ import_node_fs9 = __toESM(require("node:fs"), 1);
18523
+ import_node_path10 = __toESM(require("node:path"), 1);
18524
+ CheckoutRequiresGitError = class extends Error {
18525
+ };
18526
+ BINARY_SNIFF_BYTES = 8192;
18527
+ LIST_FILES_TIMEOUT_MS = 1e4;
18528
+ CheckoutFilePlane = class {
18529
+ root;
18530
+ entries = /* @__PURE__ */ new Map();
18531
+ sniffed = /* @__PURE__ */ new Map();
18532
+ // the deadline above, as a number a caller can set. what it changes is how
18533
+ // long a wedged git is waited on, never whether it is waited on, so proving
18534
+ // the kill does not have to cost the production deadline.
18535
+ listTimeoutMs;
18536
+ constructor(root, options = {}) {
18537
+ this.root = import_node_path10.default.resolve(root);
18538
+ this.listTimeoutMs = options.listTimeoutMs ?? LIST_FILES_TIMEOUT_MS;
18539
+ }
18540
+ index() {
18541
+ return this.entries;
18542
+ }
18543
+ async refresh() {
18544
+ let stdout;
18545
+ try {
18546
+ stdout = (0, import_node_child_process5.execFileSync)("git", ["ls-files", "-co", "--exclude-standard", "-z"], {
18547
+ cwd: this.root,
18548
+ encoding: "utf8",
18549
+ maxBuffer: 64 * 1024 * 1024,
18550
+ timeout: this.listTimeoutMs,
18551
+ // stating the environment is not redundant here. bun 1.3.14 resolves
18552
+ // the binary for the synchronous form against the PATH it captured at
18553
+ // startup and ignores a later change to `process.env.PATH`, so without
18554
+ // this it runs a different git than every other call in the process.
18555
+ env: process.env
18556
+ });
18557
+ } catch (error) {
18558
+ const message = error instanceof Error ? error.message : String(error);
18559
+ const killed = error instanceof Error && "signal" in error && error.signal === "SIGTERM";
18560
+ throw new CheckoutRequiresGitError(
18561
+ killed ? `git did not list the project's files in ${this.root} within ${this.listTimeoutMs / 1e3}s, so the box has no file set to serve. the checkout may be on a stalled filesystem, or this machine may be too loaded to start a subprocess.` : `could not list the project's files with git in ${this.root}: ${message}`
18562
+ );
18563
+ }
18564
+ if (stdout.length === 0) {
18565
+ throw new CheckoutRequiresGitError(
18566
+ `git listed no files in ${this.root}. either git tracks nothing there and nothing untracked is visible to it, or this machine could not read the child process's output.`
18567
+ );
18568
+ }
18569
+ const next = /* @__PURE__ */ new Map();
18570
+ const nextSniffed = /* @__PURE__ */ new Map();
18571
+ for (const relative of stdout.split("\0")) {
18572
+ if (!relative) continue;
18573
+ let stat2;
18574
+ try {
18575
+ stat2 = import_node_fs9.default.statSync(import_node_path10.default.join(this.root, relative));
18576
+ } catch {
18577
+ continue;
18578
+ }
18579
+ if (!stat2.isFile()) continue;
18580
+ const seen = this.sniffed.get(relative);
18581
+ const binary = seen && seen.size === stat2.size && seen.mtimeMs === stat2.mtimeMs ? seen.binary : looksBinary(import_node_path10.default.join(this.root, relative), stat2.size);
18582
+ nextSniffed.set(relative, { size: stat2.size, mtimeMs: stat2.mtimeMs, binary });
18583
+ next.set(relative, {
18584
+ size: stat2.size,
18585
+ // a checkout carries no write policy of its own; if the user can write
18586
+ // the file, so can the shell
18587
+ readOnly: false,
18588
+ binary
18589
+ });
18590
+ }
18591
+ this.entries = next;
18592
+ this.sniffed = nextSniffed;
18593
+ }
18594
+ absolute(planePath) {
18595
+ const resolved = import_node_path10.default.resolve(this.root, planePath);
18596
+ if (resolved !== this.root && !resolved.startsWith(`${this.root}${import_node_path10.default.sep}`)) {
18597
+ throw new Error(`path escapes the project: ${planePath}`);
18598
+ }
18599
+ return resolved;
18600
+ }
18601
+ async read(planePath) {
18602
+ return import_node_fs9.default.promises.readFile(this.absolute(planePath), "utf8");
18603
+ }
18604
+ async readBytes(planePath) {
18605
+ return import_node_fs9.default.promises.readFile(this.absolute(planePath));
18606
+ }
18607
+ async write(planePath, content) {
18608
+ const absolute = this.absolute(planePath);
18609
+ await import_node_fs9.default.promises.mkdir(import_node_path10.default.dirname(absolute), { recursive: true });
18610
+ await import_node_fs9.default.promises.writeFile(absolute, content);
18611
+ this.entries.set(planePath, {
18612
+ size: content.byteLength,
18613
+ readOnly: false,
18614
+ // the bytes are in hand, so the same NUL test costs nothing here
18615
+ binary: content.subarray(0, BINARY_SNIFF_BYTES).includes(0)
18616
+ });
18617
+ }
18618
+ async delete(planePath) {
18619
+ await import_node_fs9.default.promises.rm(this.absolute(planePath), { force: true });
18620
+ this.entries.delete(planePath);
18621
+ }
18622
+ };
18623
+ }
18624
+ });
18625
+
18626
+ // src/host/plane-host.ts
18627
+ var import_node_fs10, import_node_path11, import_plane, WS_OPEN2, PlaneNotRegisteredError, PlaneHost;
18628
+ var init_plane_host = __esm({
18629
+ "src/host/plane-host.ts"() {
18630
+ "use strict";
18631
+ import_node_fs10 = __toESM(require("node:fs"), 1);
18632
+ import_node_path11 = __toESM(require("node:path"), 1);
18633
+ import_plane = require("@contrast/harness/plane");
18634
+ init_checkout_plane();
18635
+ WS_OPEN2 = 1;
18636
+ PlaneNotRegisteredError = class extends Error {
18637
+ };
18638
+ PlaneHost = class {
18639
+ // roots a box authorized, each tied to the socket that authorized it
18640
+ registered = /* @__PURE__ */ new Map();
18641
+ open = /* @__PURE__ */ new Map();
18642
+ byRoot = /* @__PURE__ */ new Map();
18643
+ nextPlaneId = 1;
18644
+ /** handle a plane:* message. returns true iff it was one. */
18645
+ async handleMessage(ws, msg) {
18646
+ if (typeof msg !== "object" || msg === null) return false;
18647
+ const record = { ...msg };
18648
+ const type = record.type;
18649
+ if (typeof type !== "string" || !type.startsWith("plane:")) return false;
18650
+ const id = record.id;
18651
+ try {
18652
+ this.respond(ws, id, await this.dispatch(ws, type, record));
18653
+ } catch (err) {
18654
+ const code = err instanceof PlaneNotRegisteredError ? "plane-not-registered" : void 0;
18655
+ this.respondError(ws, id, err instanceof Error ? err.message : String(err), code);
18656
+ }
18657
+ return true;
18658
+ }
18659
+ unregisterSocket(ws) {
18660
+ for (const [root, sockets] of [...this.registered]) {
18661
+ if (!sockets.delete(ws)) continue;
18662
+ if (sockets.size > 0) continue;
18663
+ this.registered.delete(root);
18664
+ const planeId = this.byRoot.get(root);
18665
+ if (planeId) {
18666
+ this.open.delete(planeId);
18667
+ this.byRoot.delete(root);
18668
+ }
18669
+ }
18670
+ for (const [planeId, entry] of [...this.open]) {
18671
+ entry.sockets.delete(ws);
18672
+ if (entry.sockets.size === 0 && !this.registered.has(entry.root)) {
18673
+ this.open.delete(planeId);
18674
+ this.byRoot.delete(entry.root);
18675
+ }
18676
+ }
18677
+ }
18678
+ /** roots currently reachable, for the daemon's status output. */
18679
+ registeredRoots() {
18680
+ return [...this.registered.keys()];
18681
+ }
18682
+ async dispatch(ws, type, msg) {
18683
+ switch (type) {
18684
+ case "plane:register": {
18685
+ const root = this.realRoot(String(msg.root ?? ""));
18686
+ const sockets = this.registered.get(root) ?? /* @__PURE__ */ new Set();
18687
+ sockets.add(ws);
18688
+ this.registered.set(root, sockets);
18689
+ return { root, holders: sockets.size };
18690
+ }
18691
+ // READ ONLY, and that is the whole point of it existing beside the
18692
+ // `holders` field on plane:register.
18693
+ //
18694
+ // registration is the AUTHORIZATION step: it is what permits any page on
18695
+ // this machine to open that root over a loopback socket that CORS does
18696
+ // not protect. so a caller asking "is anyone else in this checkout"
18697
+ // cannot be made to authorize the root in order to find out, even
18698
+ // briefly. it also means the count is the honest one: no box reads 0 and
18699
+ // a single box reads 1, where asking through register would have made
18700
+ // every answer one higher and forced a threshold that counts the asker.
18701
+ case "plane:status": {
18702
+ const root = this.realRoot(String(msg.root ?? ""));
18703
+ const sockets = this.registered.get(root);
18704
+ return { root, holders: sockets?.size ?? 0, registered: Boolean(sockets) };
18705
+ }
18706
+ case "plane:open": {
18707
+ const root = this.realRoot(String(msg.root ?? ""));
18708
+ if (!this.registered.has(root)) {
18709
+ throw new PlaneNotRegisteredError(
18710
+ `no box has registered ${root}; start one with \`rnx box\` there`
18711
+ );
18712
+ }
18713
+ const existingId = this.byRoot.get(root);
18714
+ const existing = existingId ? this.open.get(existingId) : void 0;
18715
+ if (existingId && existing) {
18716
+ existing.sockets.add(ws);
18717
+ return { planeId: existingId, version: existing.version };
18718
+ }
18719
+ const plane = new CheckoutFilePlane(root);
18720
+ await plane.refresh();
18721
+ const planeId = `plane-${this.nextPlaneId++}`;
18722
+ const entry = {
18723
+ root,
18724
+ plane,
18725
+ version: (0, import_plane.indexVersion)(plane.index()),
18726
+ sockets: /* @__PURE__ */ new Set([ws])
18727
+ };
18728
+ this.open.set(planeId, entry);
18729
+ this.byRoot.set(root, planeId);
18730
+ return { planeId, version: entry.version };
18731
+ }
18732
+ case "plane:close": {
18733
+ const entry = this.open.get(String(msg.planeId ?? ""));
18734
+ if (entry) entry.sockets.delete(ws);
18735
+ return { ok: true };
18736
+ }
18737
+ default: {
18738
+ const entry = this.entry(msg);
18739
+ const handled = await (0, import_plane.dispatchPlaneCommand)(entry.plane, type, msg);
18740
+ if (!handled) throw new Error(`unknown plane command: ${type}`);
18741
+ if (handled.version) entry.version = handled.version;
18742
+ return handled.result;
18743
+ }
18744
+ }
18745
+ }
18746
+ entry(msg) {
18747
+ const entry = this.open.get(String(msg.planeId ?? ""));
18748
+ if (!entry) throw new Error(`unknown planeId: ${String(msg.planeId)}`);
18749
+ return entry;
18750
+ }
18751
+ // resolve symlinks and relative segments before comparing, so a registered
18752
+ // root and an opened one cannot disagree about the same directory.
18753
+ realRoot(root) {
18754
+ if (!root) throw new Error("root is required");
18755
+ const resolved = import_node_path11.default.resolve(root);
18756
+ let real;
18757
+ try {
18758
+ real = import_node_fs10.default.realpathSync(resolved);
18759
+ } catch {
18760
+ throw new Error(`no such directory: ${resolved}`);
18761
+ }
18762
+ if (!import_node_fs10.default.statSync(real).isDirectory()) throw new Error(`not a directory: ${real}`);
18763
+ return real;
18764
+ }
18765
+ respond(ws, id, result) {
18766
+ if (ws.readyState !== WS_OPEN2) return;
18767
+ try {
18768
+ ws.send(JSON.stringify({ id, result }));
18769
+ } catch {
18770
+ }
18771
+ }
18772
+ respondError(ws, id, error, code) {
18773
+ if (ws.readyState !== WS_OPEN2) return;
18774
+ try {
18775
+ ws.send(JSON.stringify({ id, error, ...code ? { code } : {} }));
18776
+ } catch {
18777
+ }
18778
+ }
18779
+ };
18780
+ }
18781
+ });
18782
+
18454
18783
  // src/host/replacement-module-handler.ts
18455
18784
  function getReplacementLoader(filePath) {
18456
- const ext = import_node_path10.default.extname(filePath).toLowerCase();
18785
+ const ext = import_node_path12.default.extname(filePath).toLowerCase();
18457
18786
  switch (ext) {
18458
18787
  case ".js":
18459
18788
  return "js";
@@ -18535,7 +18864,7 @@ async function handleReplacementModuleRequest(req, res, options = {}) {
18535
18864
  res.end("replacement files must be .js, .jsx, .ts, or .tsx");
18536
18865
  return true;
18537
18866
  }
18538
- if (!import_node_path10.default.isAbsolute(targetPath)) {
18867
+ if (!import_node_path12.default.isAbsolute(targetPath)) {
18539
18868
  res.writeHead(400, { "Content-Type": "text/plain; charset=utf-8" });
18540
18869
  res.end("replacement file path must be absolute");
18541
18870
  return true;
@@ -18543,10 +18872,10 @@ async function handleReplacementModuleRequest(req, res, options = {}) {
18543
18872
  const runtimePackages = new Set(url.searchParams.getAll("runtimePackage"));
18544
18873
  let resolvedProjectRoot;
18545
18874
  if (options.projectRoot) {
18546
- const lexicalProjectRoot = import_node_path10.default.resolve(options.projectRoot);
18875
+ const lexicalProjectRoot = import_node_path12.default.resolve(options.projectRoot);
18547
18876
  resolvedProjectRoot = await import_promises5.default.realpath(options.projectRoot);
18548
- const lexicalTarget = import_node_path10.default.relative(lexicalProjectRoot, targetPath);
18549
- if (lexicalTarget.startsWith("..") || import_node_path10.default.isAbsolute(lexicalTarget)) {
18877
+ const lexicalTarget = import_node_path12.default.relative(lexicalProjectRoot, targetPath);
18878
+ if (lexicalTarget.startsWith("..") || import_node_path12.default.isAbsolute(lexicalTarget)) {
18550
18879
  res.writeHead(403, { "Content-Type": "text/plain; charset=utf-8" });
18551
18880
  res.end("replacement file must be inside the project root");
18552
18881
  return true;
@@ -18562,8 +18891,8 @@ async function handleReplacementModuleRequest(req, res, options = {}) {
18562
18891
  return true;
18563
18892
  }
18564
18893
  if (resolvedProjectRoot) {
18565
- const relativeTarget = import_node_path10.default.relative(resolvedProjectRoot, resolvedTargetPath);
18566
- if (relativeTarget.startsWith("..") || import_node_path10.default.isAbsolute(relativeTarget)) {
18894
+ const relativeTarget = import_node_path12.default.relative(resolvedProjectRoot, resolvedTargetPath);
18895
+ if (relativeTarget.startsWith("..") || import_node_path12.default.isAbsolute(relativeTarget)) {
18567
18896
  res.writeHead(403, { "Content-Type": "text/plain; charset=utf-8" });
18568
18897
  res.end("replacement file must be inside the project root");
18569
18898
  return true;
@@ -18648,12 +18977,12 @@ exports.jsxDEV = exports.jsx`,
18648
18977
  }
18649
18978
  return true;
18650
18979
  }
18651
- var import_promises5, import_node_path10, import_esbuild, REPLACEMENT_MODULE_ROUTE;
18980
+ var import_promises5, import_node_path12, import_esbuild, REPLACEMENT_MODULE_ROUTE;
18652
18981
  var init_replacement_module_handler = __esm({
18653
18982
  "src/host/replacement-module-handler.ts"() {
18654
18983
  "use strict";
18655
18984
  import_promises5 = __toESM(require("node:fs/promises"), 1);
18656
- import_node_path10 = __toESM(require("node:path"), 1);
18985
+ import_node_path12 = __toESM(require("node:path"), 1);
18657
18986
  import_esbuild = require("esbuild");
18658
18987
  init_backend_origin();
18659
18988
  REPLACEMENT_MODULE_ROUTE = "/__sootsim-replacement-module";
@@ -18900,6 +19229,7 @@ var init_bridge_host = __esm({
18900
19229
  init_agent_host();
18901
19230
  init_fetch_proxy_handler();
18902
19231
  init_open_url();
19232
+ init_plane_host();
18903
19233
  init_replacement_module_handler();
18904
19234
  init_websocket_proxy();
18905
19235
  SOOTSIM_CROSS_ORIGIN_EMBEDDER_POLICY = "require-corp";
@@ -18964,6 +19294,7 @@ var init_bridge_host = __esm({
18964
19294
  nextCliFallbackId = 1;
18965
19295
  cliIdleTimer = null;
18966
19296
  agentHost;
19297
+ planeHost = new PlaneHost();
18967
19298
  static CLI_IDLE_TIMEOUT_MS = 6e4;
18968
19299
  static CLI_LEASE_TTL_MS = 6e5;
18969
19300
  static USER_ACTIVE_LEASE_TTL_MS = 8e3;
@@ -19195,6 +19526,10 @@ var init_bridge_host = __esm({
19195
19526
  void this.agentHost.handleMessage(ws, msg);
19196
19527
  return;
19197
19528
  }
19529
+ if (typeof msg.type === "string" && msg.type.startsWith("plane:")) {
19530
+ void this.planeHost.handleMessage(ws, msg);
19531
+ return;
19532
+ }
19198
19533
  if (msg.type === "runtime:list") {
19199
19534
  const versions = sootsimRuntime.listInstalled();
19200
19535
  const active = this.getActiveRuntime();
@@ -19517,6 +19852,7 @@ var init_bridge_host = __esm({
19517
19852
  });
19518
19853
  ws.on("close", () => {
19519
19854
  this.agentHost.unregisterSocket(ws);
19855
+ this.planeHost.unregisterSocket(ws);
19520
19856
  if (role === "sim" && sim) {
19521
19857
  this.rememberDisconnectedSim(sim);
19522
19858
  if (this.primarySimId === sim.id) {
@@ -21214,17 +21550,17 @@ function liveDevBridgeLockfiles() {
21214
21550
  return readDevBridgeLockfiles().filter((lockfile) => isDevBridgeLockfileFresh(lockfile));
21215
21551
  }
21216
21552
  function checkoutRoot(cwd) {
21217
- let current = import_node_path11.default.resolve(cwd);
21553
+ let current = import_node_path13.default.resolve(cwd);
21218
21554
  while (true) {
21219
- if (import_node_fs9.default.existsSync(import_node_path11.default.join(current, ".git"))) return current;
21220
- const parent = import_node_path11.default.dirname(current);
21555
+ if (import_node_fs11.default.existsSync(import_node_path13.default.join(current, ".git"))) return current;
21556
+ const parent = import_node_path13.default.dirname(current);
21221
21557
  if (parent === current) return null;
21222
21558
  current = parent;
21223
21559
  }
21224
21560
  }
21225
21561
  function pathContains(parent, child) {
21226
- const relative = import_node_path11.default.relative(parent, child);
21227
- return relative === "" || relative !== ".." && !relative.startsWith(`..${import_node_path11.default.sep}`) && !import_node_path11.default.isAbsolute(relative);
21562
+ const relative = import_node_path13.default.relative(parent, child);
21563
+ return relative === "" || relative !== ".." && !relative.startsWith(`..${import_node_path13.default.sep}`) && !import_node_path13.default.isAbsolute(relative);
21228
21564
  }
21229
21565
  function resolveDefaultDevBridgeLockfile() {
21230
21566
  const lockfiles = liveDevBridgeLockfiles();
@@ -21647,18 +21983,18 @@ async function evalInBridge(bridge, code, opts = {}) {
21647
21983
  if (opts.acquireLock) payload.acquireLock = true;
21648
21984
  return bridge.send(payload);
21649
21985
  }
21650
- async function callInBridge(bridge, path18, ...args) {
21651
- return bridge.send({ type: "call", path: path18, args });
21986
+ async function callInBridge(bridge, path20, ...args) {
21987
+ return bridge.send({ type: "call", path: path20, args });
21652
21988
  }
21653
- async function callInBridgeWrite(bridge, path18, ...args) {
21654
- return bridge.send({ type: "call", path: path18, args, acquireLock: true });
21989
+ async function callInBridgeWrite(bridge, path20, ...args) {
21990
+ return bridge.send({ type: "call", path: path20, args, acquireLock: true });
21655
21991
  }
21656
- var import_node_fs9, import_node_path11, import_ws3, RNX_PLACEMENT_ENV, resolvedWorld, BridgeSimLockedError;
21992
+ var import_node_fs11, import_node_path13, import_ws3, RNX_PLACEMENT_ENV, resolvedWorld, BridgeSimLockedError;
21657
21993
  var init_ws_bridge = __esm({
21658
21994
  "cli/ws-bridge.ts"() {
21659
21995
  "use strict";
21660
- import_node_fs9 = __toESM(require("node:fs"), 1);
21661
- import_node_path11 = __toESM(require("node:path"), 1);
21996
+ import_node_fs11 = __toESM(require("node:fs"), 1);
21997
+ import_node_path13 = __toESM(require("node:path"), 1);
21662
21998
  import_ws3 = require("ws");
21663
21999
  init_bridge_constants();
21664
22000
  init_cli_constants();
@@ -21753,8 +22089,8 @@ function getDefaultPortForProtocol(protocol) {
21753
22089
  return protocol === "https:" ? 443 : 80;
21754
22090
  }
21755
22091
  function isBaseServerUrl(url) {
21756
- const path18 = url.pathname || "/";
21757
- return (path18 === "/" || path18 === "") && !url.search && !url.hash;
22092
+ const path20 = url.pathname || "/";
22093
+ return (path20 === "/" || path20 === "") && !url.search && !url.hash;
21758
22094
  }
21759
22095
  async function resolvePortViaServerScan(port) {
21760
22096
  try {
@@ -21768,6 +22104,7 @@ async function resolvePortViaServerScan(port) {
21768
22104
  (s) => !!s && typeof s === "object" && s.port === port && typeof s.bundleUrl === "string"
21769
22105
  );
21770
22106
  if (!match) return null;
22107
+ if (match.bundleUrlProvisional) return null;
21771
22108
  return {
21772
22109
  // normalize exactly like the manifest-probe branch below — the scan
21773
22110
  // returns metro's manifest launchAsset url (lazy=true for code-split
@@ -21783,45 +22120,76 @@ async function resolvePortViaServerScan(port) {
21783
22120
  return null;
21784
22121
  }
21785
22122
  }
21786
- async function probeBaseUrlBundleDirect(baseUrl, port) {
21787
- const normalizedBaseUrl = baseUrl.replace(/\/+$/, "");
22123
+ async function probeExpoManifest(normalizedBaseUrl, port) {
22124
+ let body;
22125
+ const pending = fetchDevProbe(`${normalizedBaseUrl}/`, {
22126
+ headers: { "expo-platform": "ios" }
22127
+ }).then(
22128
+ async (res) => res.ok ? { ok: true, body: await res.text() } : null,
22129
+ () => void 0
22130
+ );
22131
+ const answered = await Promise.race([
22132
+ pending,
22133
+ new Promise(
22134
+ (resolve7) => setTimeout(() => resolve7("timeout"), MANIFEST_PROBE_TIMEOUT_MS).unref?.()
22135
+ )
22136
+ ]);
22137
+ if (answered === "timeout" || answered === void 0) return { state: "no-answer" };
22138
+ if (answered === null) return { state: "no-manifest" };
22139
+ body = answered.body;
22140
+ if (!body.trim()) return { state: "no-answer" };
22141
+ let manifest;
21788
22142
  try {
21789
- const manifestRes = await fetchDevProbe(`${normalizedBaseUrl}/`, {
21790
- headers: { "expo-platform": "ios" }
21791
- });
21792
- if (manifestRes.ok) {
21793
- const manifest = await manifestRes.json();
21794
- const client = manifest?.extra?.expoClient || manifest?.extra || {};
21795
- const rawLaunchUrl = typeof manifest?.launchAsset?.url === "string" ? manifest.launchAsset.url : void 0;
21796
- const launchUrl2 = rawLaunchUrl ? alignLoopbackBundleUrlWithProbe(rawLaunchUrl, normalizedBaseUrl) : void 0;
21797
- if (launchUrl2 || client.name) {
21798
- return {
21799
- bundleUrl: normalizeNativeDevBundleUrl(
21800
- launchUrl2 || `${normalizedBaseUrl}${METRO_BUNDLE_PATH}`
21801
- ),
21802
- port,
21803
- framework: inferManifestFramework(launchUrl2, client.sdkVersion),
21804
- projectName: client.name
21805
- };
21806
- }
21807
- }
22143
+ manifest = JSON.parse(body);
21808
22144
  } catch {
22145
+ return { state: "no-manifest" };
21809
22146
  }
21810
- try {
21811
- const statusRes = await fetchDevProbe(`${normalizedBaseUrl}/status`);
21812
- if (statusRes.ok) {
21813
- const text = await statusRes.text();
21814
- if (text.includes("packager-status:running")) {
21815
- return {
21816
- bundleUrl: `${normalizedBaseUrl}${METRO_BUNDLE_PATH}`,
21817
- port,
21818
- framework: "metro"
21819
- };
21820
- }
22147
+ const client = manifest?.extra?.expoClient || manifest?.extra || {};
22148
+ const rawLaunchUrl = typeof manifest?.launchAsset?.url === "string" ? manifest.launchAsset.url : void 0;
22149
+ const launchUrl2 = rawLaunchUrl ? alignLoopbackBundleUrlWithProbe(rawLaunchUrl, normalizedBaseUrl) : void 0;
22150
+ if (!launchUrl2 && !client.name) return { state: "no-manifest" };
22151
+ return {
22152
+ state: "resolved",
22153
+ bundle: {
22154
+ bundleUrl: normalizeNativeDevBundleUrl(
22155
+ launchUrl2 || `${normalizedBaseUrl}${METRO_BUNDLE_PATH}`
22156
+ ),
22157
+ port,
22158
+ framework: inferManifestFramework(launchUrl2, client.sdkVersion),
22159
+ projectName: client.name
21821
22160
  }
22161
+ };
22162
+ }
22163
+ async function isPackagerRunning(normalizedBaseUrl) {
22164
+ try {
22165
+ const res = await fetchDevProbe(`${normalizedBaseUrl}/status`);
22166
+ if (!res.ok) return false;
22167
+ return (await res.text()).includes("packager-status:running");
21822
22168
  } catch {
22169
+ return false;
21823
22170
  }
21824
- return null;
22171
+ }
22172
+ async function probeBaseUrlBundleDirect(baseUrl, port) {
22173
+ const normalizedBaseUrl = baseUrl.replace(/\/+$/, "");
22174
+ let manifest = await probeExpoManifest(normalizedBaseUrl, port);
22175
+ let packagerConfirmed = false;
22176
+ if (manifest.state === "no-answer") {
22177
+ packagerConfirmed = await isPackagerRunning(normalizedBaseUrl);
22178
+ if (!packagerConfirmed) return null;
22179
+ const deadline = Date.now() + MANIFEST_BOOT_BUDGET_MS;
22180
+ while (manifest.state === "no-answer" && Date.now() < deadline) {
22181
+ await new Promise((resolve7) => setTimeout(resolve7, MANIFEST_BOOT_RETRY_MS));
22182
+ manifest = await probeExpoManifest(normalizedBaseUrl, port);
22183
+ }
22184
+ }
22185
+ if (manifest.state === "resolved") return manifest.bundle;
22186
+ if (manifest.state === "no-answer") return null;
22187
+ if (!packagerConfirmed && !await isPackagerRunning(normalizedBaseUrl)) return null;
22188
+ return {
22189
+ bundleUrl: `${normalizedBaseUrl}${METRO_BUNDLE_PATH}`,
22190
+ port,
22191
+ framework: "metro"
22192
+ };
21825
22193
  }
21826
22194
  async function probeBaseUrlBundle(baseUrl, port) {
21827
22195
  try {
@@ -21870,11 +22238,15 @@ async function resolveConnectionInput(input) {
21870
22238
  }
21871
22239
  return { bundleUrl: parsed.toString(), port, framework: "unknown" };
21872
22240
  }
22241
+ var MANIFEST_PROBE_TIMEOUT_MS, MANIFEST_BOOT_BUDGET_MS, MANIFEST_BOOT_RETRY_MS;
21873
22242
  var init_dev_bundle_resolution = __esm({
21874
22243
  "src/dev-bundle-resolution.ts"() {
21875
22244
  "use strict";
21876
22245
  init_backend_origin();
21877
22246
  init_native_dev_bundle_url();
22247
+ MANIFEST_PROBE_TIMEOUT_MS = 5e3;
22248
+ MANIFEST_BOOT_BUDGET_MS = 3e4;
22249
+ MANIFEST_BOOT_RETRY_MS = 1e3;
21878
22250
  }
21879
22251
  });
21880
22252
 
@@ -21985,9 +22357,9 @@ var init_profiles = __esm({
21985
22357
  // cli/app-project.ts
21986
22358
  function firstNamedFile(dir, names) {
21987
22359
  for (const name2 of names) {
21988
- const candidate = import_node_path12.default.join(dir, name2);
22360
+ const candidate = import_node_path14.default.join(dir, name2);
21989
22361
  try {
21990
- if (import_node_fs10.default.statSync(candidate).isFile()) return candidate;
22362
+ if (import_node_fs12.default.statSync(candidate).isFile()) return candidate;
21991
22363
  } catch {
21992
22364
  }
21993
22365
  }
@@ -22000,24 +22372,24 @@ function findSootSimAppConfig(dir) {
22000
22372
  return firstNamedFile(dir, SOOTSIM_APP_CONFIG_NAMES);
22001
22373
  }
22002
22374
  function resolveAppProject(projectRoot) {
22003
- const appDir = import_node_path12.default.resolve(projectRoot);
22375
+ const appDir = import_node_path14.default.resolve(projectRoot);
22004
22376
  try {
22005
- if (!import_node_fs10.default.statSync(appDir).isDirectory()) return null;
22377
+ if (!import_node_fs12.default.statSync(appDir).isDirectory()) return null;
22006
22378
  } catch {
22007
22379
  return null;
22008
22380
  }
22009
22381
  const configPath = findSootSimAppConfig(appDir);
22010
- if (!configPath && !findExpoAppConfig(appDir) && !import_node_fs10.default.existsSync(import_node_path12.default.join(appDir, "package.json"))) {
22382
+ if (!configPath && !findExpoAppConfig(appDir) && !import_node_fs12.default.existsSync(import_node_path14.default.join(appDir, "package.json"))) {
22011
22383
  return null;
22012
22384
  }
22013
22385
  return { appDir, configPath };
22014
22386
  }
22015
- var import_node_fs10, import_node_path12, EXPO_APP_CONFIG_NAMES, SOOTSIM_APP_CONFIG_NAMES;
22387
+ var import_node_fs12, import_node_path14, EXPO_APP_CONFIG_NAMES, SOOTSIM_APP_CONFIG_NAMES;
22016
22388
  var init_app_project = __esm({
22017
22389
  "cli/app-project.ts"() {
22018
22390
  "use strict";
22019
- import_node_fs10 = __toESM(require("node:fs"), 1);
22020
- import_node_path12 = __toESM(require("node:path"), 1);
22391
+ import_node_fs12 = __toESM(require("node:fs"), 1);
22392
+ import_node_path14 = __toESM(require("node:path"), 1);
22021
22393
  EXPO_APP_CONFIG_NAMES = [
22022
22394
  "app.config.js",
22023
22395
  "app.config.ts",
@@ -22046,13 +22418,13 @@ function isSootSimConfig(value) {
22046
22418
  }
22047
22419
  function resolveConfigReplacementFiles(config, configPath) {
22048
22420
  if (!config.modules) return config;
22049
- const configDir = import_node_path13.default.dirname(configPath);
22421
+ const configDir = import_node_path15.default.dirname(configPath);
22050
22422
  return {
22051
22423
  ...config,
22052
22424
  modules: Object.fromEntries(
22053
22425
  Object.entries(config.modules).map(([name2, resolution]) => [
22054
22426
  name2,
22055
- resolution && typeof resolution === "object" && "file" in resolution ? { ...resolution, file: import_node_path13.default.resolve(configDir, resolution.file) } : resolution
22427
+ resolution && typeof resolution === "object" && "file" in resolution ? { ...resolution, file: import_node_path15.default.resolve(configDir, resolution.file) } : resolution
22056
22428
  ])
22057
22429
  )
22058
22430
  };
@@ -22082,12 +22454,12 @@ async function resolveSootSimAppConfig(startDir = process.cwd()) {
22082
22454
  const project = resolveAppProject(startDir);
22083
22455
  return { project, config: await loadSootSimAppConfig(project) };
22084
22456
  }
22085
- var import_node_module, import_node_path13, import_jiti, require2;
22457
+ var import_node_module, import_node_path15, import_jiti, require2;
22086
22458
  var init_app_config = __esm({
22087
22459
  "cli/app-config.ts"() {
22088
22460
  "use strict";
22089
22461
  import_node_module = require("node:module");
22090
- import_node_path13 = __toESM(require("node:path"), 1);
22462
+ import_node_path15 = __toESM(require("node:path"), 1);
22091
22463
  init_src();
22092
22464
  import_jiti = require("jiti");
22093
22465
  init_public_brand();
@@ -22155,13 +22527,13 @@ function discoverNativeLinkedAppFonts(opts) {
22155
22527
  scannedFontDirectories++;
22156
22528
  let entries;
22157
22529
  try {
22158
- entries = import_node_fs11.default.readdirSync(current.dir, { withFileTypes: true });
22530
+ entries = import_node_fs13.default.readdirSync(current.dir, { withFileTypes: true });
22159
22531
  } catch {
22160
22532
  continue;
22161
22533
  }
22162
22534
  for (const entry of entries) {
22163
22535
  if (entry.isSymbolicLink()) continue;
22164
- const fullPath = import_node_path14.default.join(current.dir, entry.name);
22536
+ const fullPath = import_node_path16.default.join(current.dir, entry.name);
22165
22537
  if (entry.isDirectory()) {
22166
22538
  if (current.depth < 4 && pending.length < 64 && !entry.name.startsWith(".")) {
22167
22539
  pending.push({ dir: fullPath, depth: current.depth + 1 });
@@ -22169,15 +22541,15 @@ function discoverNativeLinkedAppFonts(opts) {
22169
22541
  continue;
22170
22542
  }
22171
22543
  if (!entry.isFile() || !/\.(ttf|otf|woff|woff2)$/i.test(entry.name)) continue;
22172
- const relative = import_node_path14.default.relative(root, fullPath).split(import_node_path14.default.sep).join("/");
22173
- fontPaths.push(import_node_path14.default.posix.join(metroPathPrefix, relative));
22544
+ const relative = import_node_path16.default.relative(root, fullPath).split(import_node_path16.default.sep).join("/");
22545
+ fontPaths.push(import_node_path16.default.posix.join(metroPathPrefix, relative));
22174
22546
  if (fontPaths.length >= 256) break;
22175
22547
  }
22176
22548
  }
22177
22549
  };
22178
22550
  try {
22179
22551
  const packageJson = JSON.parse(
22180
- import_node_fs11.default.readFileSync(import_node_path14.default.join(selectedAppDir, "package.json"), "utf8")
22552
+ import_node_fs13.default.readFileSync(import_node_path16.default.join(selectedAppDir, "package.json"), "utf8")
22181
22553
  );
22182
22554
  const dependencyNames = /* @__PURE__ */ new Set();
22183
22555
  for (const field of ["dependencies", "devDependencies", "optionalDependencies"]) {
@@ -22192,24 +22564,24 @@ function discoverNativeLinkedAppFonts(opts) {
22192
22564
  const config = Reflect.get(packageJson, "reactNativeVectorIcons");
22193
22565
  const configuredFontDir = config && typeof config === "object" ? Reflect.get(config, "fontDir") : null;
22194
22566
  const configuredDir = typeof configuredFontDir === "string" ? configuredFontDir : "rnvi-fonts";
22195
- const root = import_node_path14.default.resolve(selectedAppDir, configuredDir);
22196
- const relativeRoot = import_node_path14.default.relative(selectedAppDir, root);
22197
- if (!relativeRoot.startsWith("..") && !import_node_path14.default.isAbsolute(relativeRoot)) {
22198
- collectFontFiles(root, relativeRoot.split(import_node_path14.default.sep).join("/"));
22567
+ const root = import_node_path16.default.resolve(selectedAppDir, configuredDir);
22568
+ const relativeRoot = import_node_path16.default.relative(selectedAppDir, root);
22569
+ if (!relativeRoot.startsWith("..") && !import_node_path16.default.isAbsolute(relativeRoot)) {
22570
+ collectFontFiles(root, relativeRoot.split(import_node_path16.default.sep).join("/"));
22199
22571
  }
22200
- const vectorIconRequire = (0, import_node_module2.createRequire)(import_node_path14.default.join(selectedAppDir, "package.json"));
22572
+ const vectorIconRequire = (0, import_node_module2.createRequire)(import_node_path16.default.join(selectedAppDir, "package.json"));
22201
22573
  for (const dependencyName of vectorIconDependencies) {
22202
22574
  try {
22203
22575
  const entryPath = vectorIconRequire.resolve(dependencyName);
22204
- const dependencyPath = dependencyName.split("/").join(import_node_path14.default.sep);
22205
- const marker = `${import_node_path14.default.sep}node_modules${import_node_path14.default.sep}${dependencyPath}${import_node_path14.default.sep}`;
22576
+ const dependencyPath = dependencyName.split("/").join(import_node_path16.default.sep);
22577
+ const marker = `${import_node_path16.default.sep}node_modules${import_node_path16.default.sep}${dependencyPath}${import_node_path16.default.sep}`;
22206
22578
  const markerIndex = entryPath.lastIndexOf(marker);
22207
22579
  if (markerIndex === -1) continue;
22208
22580
  const packageRoot = entryPath.slice(0, markerIndex + marker.length - 1);
22209
22581
  const fontsDir = dependencyName === "react-native-vector-icons" ? "Fonts" : "fonts";
22210
22582
  collectFontFiles(
22211
- import_node_path14.default.join(packageRoot, fontsDir),
22212
- import_node_path14.default.posix.join("node_modules", dependencyName, fontsDir)
22583
+ import_node_path16.default.join(packageRoot, fontsDir),
22584
+ import_node_path16.default.posix.join("node_modules", dependencyName, fontsDir)
22213
22585
  );
22214
22586
  } catch {
22215
22587
  }
@@ -22218,37 +22590,37 @@ function discoverNativeLinkedAppFonts(opts) {
22218
22590
  } catch {
22219
22591
  }
22220
22592
  try {
22221
- const configPath = import_node_path14.default.join(selectedAppDir, "react-native.config.js");
22222
- if (import_node_fs11.default.statSync(configPath).isFile()) {
22223
- const appConfigRequire = (0, import_node_module2.createRequire)(import_node_path14.default.join(selectedAppDir, "package.json"));
22593
+ const configPath = import_node_path16.default.join(selectedAppDir, "react-native.config.js");
22594
+ if (import_node_fs13.default.statSync(configPath).isFile()) {
22595
+ const appConfigRequire = (0, import_node_module2.createRequire)(import_node_path16.default.join(selectedAppDir, "package.json"));
22224
22596
  const rnConfig = appConfigRequire(configPath);
22225
22597
  const assets = rnConfig && typeof rnConfig === "object" ? Reflect.get(rnConfig, "assets") : null;
22226
22598
  if (Array.isArray(assets)) {
22227
22599
  for (const assetDir of assets) {
22228
22600
  if (typeof assetDir !== "string") continue;
22229
- const root = import_node_path14.default.resolve(selectedAppDir, assetDir);
22230
- const relativeRoot = import_node_path14.default.relative(selectedAppDir, root);
22231
- if (relativeRoot.startsWith("..") || import_node_path14.default.isAbsolute(relativeRoot)) continue;
22232
- collectFontFiles(root, relativeRoot.split(import_node_path14.default.sep).join("/"));
22601
+ const root = import_node_path16.default.resolve(selectedAppDir, assetDir);
22602
+ const relativeRoot = import_node_path16.default.relative(selectedAppDir, root);
22603
+ if (relativeRoot.startsWith("..") || import_node_path16.default.isAbsolute(relativeRoot)) continue;
22604
+ collectFontFiles(root, relativeRoot.split(import_node_path16.default.sep).join("/"));
22233
22605
  }
22234
22606
  }
22235
22607
  }
22236
22608
  } catch {
22237
22609
  }
22238
22610
  if (platform === "ios") {
22239
- const iosDir = import_node_path14.default.join(selectedAppDir, "ios");
22611
+ const iosDir = import_node_path16.default.join(selectedAppDir, "ios");
22240
22612
  try {
22241
- const targetDirs = import_node_fs11.default.readdirSync(iosDir, { withFileTypes: true }).filter(
22613
+ const targetDirs = import_node_fs13.default.readdirSync(iosDir, { withFileTypes: true }).filter(
22242
22614
  (entry) => entry.isDirectory() && !entry.isSymbolicLink() && entry.name !== "Pods" && entry.name !== "build" && !entry.name.startsWith(".")
22243
22615
  ).slice(0, 64);
22244
- const plistPaths = [import_node_path14.default.join(iosDir, "Info.plist")];
22616
+ const plistPaths = [import_node_path16.default.join(iosDir, "Info.plist")];
22245
22617
  for (const entry of targetDirs) {
22246
- plistPaths.push(import_node_path14.default.join(iosDir, entry.name, "Info.plist"));
22618
+ plistPaths.push(import_node_path16.default.join(iosDir, entry.name, "Info.plist"));
22247
22619
  }
22248
22620
  for (const plistPath of plistPaths) {
22249
22621
  let parsed;
22250
22622
  try {
22251
- parsed = parsePlistSource(import_node_fs11.default.readFileSync(plistPath, "utf8"));
22623
+ parsed = parsePlistSource(import_node_fs13.default.readFileSync(plistPath, "utf8"));
22252
22624
  } catch {
22253
22625
  continue;
22254
22626
  }
@@ -22258,21 +22630,21 @@ function discoverNativeLinkedAppFonts(opts) {
22258
22630
  for (const declaredFont of declaredFonts) {
22259
22631
  if (typeof declaredFont !== "string") continue;
22260
22632
  const candidates = [
22261
- import_node_path14.default.resolve(import_node_path14.default.dirname(plistPath), declaredFont),
22262
- import_node_path14.default.resolve(iosDir, declaredFont)
22633
+ import_node_path16.default.resolve(import_node_path16.default.dirname(plistPath), declaredFont),
22634
+ import_node_path16.default.resolve(iosDir, declaredFont)
22263
22635
  ];
22264
22636
  const fontPath = candidates.find((candidate) => {
22265
- const relative = import_node_path14.default.relative(selectedAppDir, candidate);
22266
- if (relative.startsWith("..") || import_node_path14.default.isAbsolute(relative)) return false;
22637
+ const relative = import_node_path16.default.relative(selectedAppDir, candidate);
22638
+ if (relative.startsWith("..") || import_node_path16.default.isAbsolute(relative)) return false;
22267
22639
  try {
22268
- return import_node_fs11.default.statSync(candidate).isFile();
22640
+ return import_node_fs13.default.statSync(candidate).isFile();
22269
22641
  } catch {
22270
22642
  return false;
22271
22643
  }
22272
22644
  });
22273
22645
  if (!fontPath) continue;
22274
22646
  fontPaths.push(
22275
- import_node_path14.default.relative(selectedAppDir, fontPath).split(import_node_path14.default.sep).join("/")
22647
+ import_node_path16.default.relative(selectedAppDir, fontPath).split(import_node_path16.default.sep).join("/")
22276
22648
  );
22277
22649
  }
22278
22650
  }
@@ -22311,13 +22683,13 @@ async function resolveMetroProjectRoot(bundleUrl, opts) {
22311
22683
  function encodeAppFontsWire(specs) {
22312
22684
  return specs.map((spec) => `${encodeURIComponent(spec.url)}::${encodeURIComponent(spec.family)}`).join(",");
22313
22685
  }
22314
- var import_node_fs11, import_node_module2, import_node_path14, import_config_plugin_fonts, require3;
22686
+ var import_node_fs13, import_node_module2, import_node_path16, import_config_plugin_fonts, require3;
22315
22687
  var init_app_fonts = __esm({
22316
22688
  "cli/app-fonts.ts"() {
22317
22689
  "use strict";
22318
- import_node_fs11 = __toESM(require("node:fs"), 1);
22690
+ import_node_fs13 = __toESM(require("node:fs"), 1);
22319
22691
  import_node_module2 = require("node:module");
22320
- import_node_path14 = __toESM(require("node:path"), 1);
22692
+ import_node_path16 = __toESM(require("node:path"), 1);
22321
22693
  import_config_plugin_fonts = require("sootsim-engine/engine/config-plugin-fonts");
22322
22694
  init_dev_server_scanner();
22323
22695
  init_plist();
@@ -23408,7 +23780,7 @@ function resolvePlaywright() {
23408
23780
  try {
23409
23781
  const modulePath = requireFromRoot.resolve(spec);
23410
23782
  const packageJsonPath = requireFromRoot.resolve(`${spec}/package.json`);
23411
- const parsed = JSON.parse((0, import_node_fs12.readFileSync)(packageJsonPath, "utf8"));
23783
+ const parsed = JSON.parse((0, import_node_fs14.readFileSync)(packageJsonPath, "utf8"));
23412
23784
  if (!isRecord3(parsed) || typeof parsed.version !== "string") continue;
23413
23785
  const bin = parsed.bin;
23414
23786
  let binPath = null;
@@ -23420,8 +23792,8 @@ function resolvePlaywright() {
23420
23792
  binPath = typeof named === "string" ? named : typeof first === "string" ? first : null;
23421
23793
  }
23422
23794
  if (!binPath) continue;
23423
- const cliPath = (0, import_node_path15.join)((0, import_node_path15.dirname)(packageJsonPath), binPath);
23424
- if (!(0, import_node_fs12.existsSync)(cliPath)) continue;
23795
+ const cliPath = (0, import_node_path17.join)((0, import_node_path17.dirname)(packageJsonPath), binPath);
23796
+ if (!(0, import_node_fs14.existsSync)(cliPath)) continue;
23425
23797
  return {
23426
23798
  spec,
23427
23799
  modulePath,
@@ -23437,7 +23809,7 @@ function resolvePlaywright() {
23437
23809
  return null;
23438
23810
  }
23439
23811
  function probeBrowser(resolved, env) {
23440
- const probe3 = (0, import_node_child_process5.spawnSync)(process.execPath, ["-e", PLAYWRIGHT_BROWSER_PROBE], {
23812
+ const probe3 = (0, import_node_child_process6.spawnSync)(process.execPath, ["-e", PLAYWRIGHT_BROWSER_PROBE], {
23441
23813
  env: {
23442
23814
  ...env,
23443
23815
  SOOTSIM_PW_MODULE: resolved.modulePath
@@ -23460,7 +23832,7 @@ function probeBrowser(resolved, env) {
23460
23832
  return {
23461
23833
  ok: true,
23462
23834
  executablePath: parsed.executablePath,
23463
- exists: parsed.exists && (0, import_node_fs12.existsSync)(parsed.executablePath)
23835
+ exists: parsed.exists && (0, import_node_fs14.existsSync)(parsed.executablePath)
23464
23836
  };
23465
23837
  } catch (error) {
23466
23838
  return {
@@ -23475,7 +23847,7 @@ function ensurePlaywrightBrowser(resolved, env = process.env) {
23475
23847
  if (before.exists) {
23476
23848
  return { ok: true, executablePath: before.executablePath, installed: false };
23477
23849
  }
23478
- const install = (0, import_node_child_process5.spawnSync)(process.execPath, [resolved.cliPath, "install", "chromium"], {
23850
+ const install = (0, import_node_child_process6.spawnSync)(process.execPath, [resolved.cliPath, "install", "chromium"], {
23479
23851
  env,
23480
23852
  encoding: "utf8",
23481
23853
  timeout: 10 * 6e4
@@ -23499,14 +23871,14 @@ ${detail}` : ""}`
23499
23871
  }
23500
23872
  return { ok: true, executablePath: after.executablePath, installed: true };
23501
23873
  }
23502
- var import_node_child_process5, import_node_fs12, import_node_module4, import_node_path15, PLAYWRIGHT_BROWSER_PROBE;
23874
+ var import_node_child_process6, import_node_fs14, import_node_module4, import_node_path17, PLAYWRIGHT_BROWSER_PROBE;
23503
23875
  var init_playwright_provisioning = __esm({
23504
23876
  "cli/drivers/playwright-provisioning.ts"() {
23505
23877
  "use strict";
23506
- import_node_child_process5 = require("node:child_process");
23507
- import_node_fs12 = require("node:fs");
23878
+ import_node_child_process6 = require("node:child_process");
23879
+ import_node_fs14 = require("node:fs");
23508
23880
  import_node_module4 = require("node:module");
23509
- import_node_path15 = require("node:path");
23881
+ import_node_path17 = require("node:path");
23510
23882
  PLAYWRIGHT_BROWSER_PROBE = `
23511
23883
  const { existsSync } = require('node:fs');
23512
23884
  const modulePath = process.env.SOOTSIM_PW_MODULE;
@@ -24182,6 +24554,30 @@ var init_open_url2 = __esm({
24182
24554
  }
24183
24555
  });
24184
24556
 
24557
+ // cli/run-rnx.ts
24558
+ function rethrowIfExit(error) {
24559
+ if (error instanceof RnxExit) throw error;
24560
+ }
24561
+ function rnxExit(code) {
24562
+ throw new RnxExit(code);
24563
+ }
24564
+ var import_node_async_hooks, RnxExit, sinks;
24565
+ var init_run_rnx = __esm({
24566
+ "cli/run-rnx.ts"() {
24567
+ "use strict";
24568
+ import_node_async_hooks = require("node:async_hooks");
24569
+ RnxExit = class extends Error {
24570
+ code;
24571
+ constructor(code) {
24572
+ super(`rnx exited with code ${code}`);
24573
+ this.name = "RnxExit";
24574
+ this.code = code;
24575
+ }
24576
+ };
24577
+ sinks = new import_node_async_hooks.AsyncLocalStorage();
24578
+ }
24579
+ });
24580
+
24185
24581
  // cli/commands/inspect/env.ts
24186
24582
  function isAgentEnv() {
24187
24583
  if (process.env.SOOTSIM_AGENT === "0") return false;
@@ -24285,7 +24681,7 @@ async function runRuntime(args, opts = {}) {
24285
24681
  default:
24286
24682
  console.error(` unknown runtime subcommand: ${sub}`);
24287
24683
  printHelp();
24288
- process.exit(1);
24684
+ rnxExit(1);
24289
24685
  }
24290
24686
  }
24291
24687
  function printHelp() {
@@ -24342,7 +24738,7 @@ async function runtimeInstall(args, opts) {
24342
24738
  return result;
24343
24739
  } catch (err) {
24344
24740
  console.error(` ${describeError(err)}`);
24345
- process.exit(1);
24741
+ rnxExit(1);
24346
24742
  }
24347
24743
  }
24348
24744
  async function activateVersion(version) {
@@ -24433,14 +24829,14 @@ async function runtimeUse(args) {
24433
24829
  const version = args[0];
24434
24830
  if (!version) {
24435
24831
  console.error(` usage: rnx runtime use <version>`);
24436
- process.exit(1);
24832
+ rnxExit(1);
24437
24833
  }
24438
24834
  const installed2 = sootsimRuntime.listInstalled();
24439
24835
  if (!installed2.includes(version)) {
24440
24836
  console.error(` version ${version} is not installed`);
24441
24837
  console.error(` installed: ${installed2.join(", ") || "(none)"}`);
24442
24838
  console.error(` run \`rnx runtime install ${version}\` first`);
24443
- process.exit(1);
24839
+ rnxExit(1);
24444
24840
  }
24445
24841
  await activateVersion(version);
24446
24842
  }
@@ -24448,7 +24844,7 @@ async function runtimeRemove(args) {
24448
24844
  const version = args[0];
24449
24845
  if (!version) {
24450
24846
  console.error(` usage: rnx runtime remove <version>`);
24451
- process.exit(1);
24847
+ rnxExit(1);
24452
24848
  }
24453
24849
  const active = readActiveRuntime();
24454
24850
  if (active === version) {
@@ -24456,11 +24852,11 @@ async function runtimeRemove(args) {
24456
24852
  console.error(
24457
24853
  ` switch with \`rnx runtime use <other>\` first, or install another version`
24458
24854
  );
24459
- process.exit(1);
24855
+ rnxExit(1);
24460
24856
  }
24461
24857
  if (readLiveRuntimeVersions().includes(version)) {
24462
24858
  console.error(` cannot remove runtime ${version} while a project is using it`);
24463
- process.exit(1);
24859
+ rnxExit(1);
24464
24860
  }
24465
24861
  const dir = runtimeDir(version);
24466
24862
  if (!import_fs8.default.existsSync(dir)) {
@@ -24481,7 +24877,7 @@ async function runtimeWhich() {
24481
24877
  async function runtimeNotes(args) {
24482
24878
  if (args.length > 0) {
24483
24879
  console.error(` usage: rnx runtime notes`);
24484
- process.exit(1);
24880
+ rnxExit(1);
24485
24881
  }
24486
24882
  const { config } = await resolveSootSimAppConfig();
24487
24883
  const version = config?.runtimeVersion ?? readActiveRuntime();
@@ -24532,6 +24928,7 @@ var init_runtime = __esm({
24532
24928
  init_home_paths();
24533
24929
  init_runtime_delivery();
24534
24930
  init_app_config();
24931
+ init_run_rnx();
24535
24932
  init_runtime_notes();
24536
24933
  init_ws_bridge();
24537
24934
  }
@@ -24656,21 +25053,21 @@ function assertExplicitBridgeLock(preferredPort, explicitPort, lock) {
24656
25053
  }
24657
25054
  async function ensureRuntimeForOpen() {
24658
25055
  const active = readActiveRuntime();
24659
- if (active && import_node_fs13.default.existsSync(runtimeDir(active))) return;
25056
+ if (active && import_node_fs15.default.existsSync(runtimeDir(active))) return;
24660
25057
  console.error(" installing rnx engine runtime...");
24661
25058
  const { runRuntime: runRuntime2 } = await Promise.resolve().then(() => (init_runtime(), runtime_exports));
24662
25059
  await runRuntime2(["install"], {});
24663
25060
  }
24664
25061
  function spawnSootsim(args) {
24665
25062
  const entry = process.argv[1];
24666
- if (entry && import_node_fs13.default.existsSync(entry)) {
24667
- return (0, import_node_child_process6.spawn)(process.execPath, [entry, ...args], {
25063
+ if (entry && import_node_fs15.default.existsSync(entry)) {
25064
+ return (0, import_node_child_process7.spawn)(process.execPath, [entry, ...args], {
24668
25065
  detached: true,
24669
25066
  stdio: "ignore",
24670
25067
  env: process.env
24671
25068
  });
24672
25069
  }
24673
- return (0, import_node_child_process6.spawn)(rnxPublicBrand.commandName, args, {
25070
+ return (0, import_node_child_process7.spawn)(rnxPublicBrand.commandName, args, {
24674
25071
  detached: true,
24675
25072
  stdio: "ignore",
24676
25073
  env: process.env
@@ -24751,7 +25148,7 @@ async function resolveConnectionInputForCli(target) {
24751
25148
  const registeredPort = /^\d+$/.test(normalized) ? Number(normalized) : localBasePort(normalized);
24752
25149
  if (registeredPort && registeredPort > 0) {
24753
25150
  const discovered = await probePort(registeredPort);
24754
- if (discovered) {
25151
+ if (discovered && !discovered.bundleUrlProvisional) {
24755
25152
  return {
24756
25153
  bundleUrl: discovered.bundleUrl,
24757
25154
  port: discovered.port,
@@ -24810,10 +25207,10 @@ async function buildResolvedBundleOpenUrl(target, baseUrl, opts = {}) {
24810
25207
  }
24811
25208
  function normalizeReplacementPath(filePath) {
24812
25209
  if (filePath.startsWith("~/")) {
24813
- return import_node_path16.default.join(import_node_os3.default.homedir(), filePath.slice(2));
25210
+ return import_node_path18.default.join(import_node_os3.default.homedir(), filePath.slice(2));
24814
25211
  }
24815
- if (import_node_path16.default.isAbsolute(filePath)) return filePath;
24816
- return import_node_path16.default.resolve(process.cwd(), filePath);
25212
+ if (import_node_path18.default.isAbsolute(filePath)) return filePath;
25213
+ return import_node_path18.default.resolve(process.cwd(), filePath);
24817
25214
  }
24818
25215
  function parseOpenRuntimeConfig(args) {
24819
25216
  const modules = {};
@@ -24823,18 +25220,18 @@ function parseOpenRuntimeConfig(args) {
24823
25220
  const raw = args[i + 1];
24824
25221
  if (!raw) {
24825
25222
  console.error(" rnx open: --replace expects <module>=<file>");
24826
- process.exit(1);
25223
+ rnxExit(1);
24827
25224
  }
24828
25225
  const eqIndex = raw.indexOf("=");
24829
25226
  if (eqIndex <= 0 || eqIndex === raw.length - 1) {
24830
25227
  console.error(" rnx open: --replace expects <module>=<file>");
24831
- process.exit(1);
25228
+ rnxExit(1);
24832
25229
  }
24833
25230
  const moduleName = raw.slice(0, eqIndex).trim();
24834
25231
  const filePath = normalizeReplacementPath(raw.slice(eqIndex + 1).trim());
24835
- if (!import_node_fs13.default.existsSync(filePath)) {
25232
+ if (!import_node_fs15.default.existsSync(filePath)) {
24836
25233
  console.error(` rnx open: replacement file not found: ${filePath}`);
24837
- process.exit(1);
25234
+ rnxExit(1);
24838
25235
  }
24839
25236
  modules[moduleName] = { file: filePath };
24840
25237
  i++;
@@ -24845,7 +25242,7 @@ function parseOpenRuntimeConfig(args) {
24845
25242
  const eqIndex = raw ? raw.indexOf("=") : -1;
24846
25243
  if (!raw || eqIndex <= 0 || eqIndex === raw.length - 1) {
24847
25244
  console.error(" rnx open: --remap expects <host:port>=<host:port>");
24848
- process.exit(1);
25245
+ rnxExit(1);
24849
25246
  }
24850
25247
  remap[raw.slice(0, eqIndex).trim()] = raw.slice(eqIndex + 1).trim();
24851
25248
  i++;
@@ -24863,7 +25260,7 @@ async function resolveOpenAppProject(resolvedBundle) {
24863
25260
  return advertisedRoot ? resolveAppProject(advertisedRoot) : null;
24864
25261
  }
24865
25262
  async function ensureConfiguredRuntime(version) {
24866
- if (import_node_fs13.default.existsSync(import_node_path16.default.join(runtimeDir(version), "index.html"))) return;
25263
+ if (import_node_fs15.default.existsSync(import_node_path18.default.join(runtimeDir(version), "index.html"))) return;
24867
25264
  console.error(` installing configured engine runtime ${version}...`);
24868
25265
  const result = await sootsimRuntime.install({
24869
25266
  version,
@@ -25223,7 +25620,7 @@ function playwrightHostPidForSim(sim) {
25223
25620
  function signalDriverLaunchConnected(result) {
25224
25621
  if (!result.connectAckFile) return false;
25225
25622
  try {
25226
- import_node_fs13.default.writeFileSync(
25623
+ import_node_fs15.default.writeFileSync(
25227
25624
  result.connectAckFile,
25228
25625
  `${JSON.stringify({ connectedAt: Date.now(), pid: result.pid ?? null })}
25229
25626
  `,
@@ -25237,19 +25634,19 @@ function signalDriverLaunchConnected(result) {
25237
25634
  function printDriverDiagnosticLogTail(result) {
25238
25635
  if (!result.diagnosticLogPath) return;
25239
25636
  try {
25240
- const detail = import_node_fs13.default.readFileSync(result.diagnosticLogPath, "utf8").trim();
25637
+ const detail = import_node_fs15.default.readFileSync(result.diagnosticLogPath, "utf8").trim();
25241
25638
  if (!detail) {
25242
- console.error(` ${import_node_path16.default.basename(result.diagnosticLogPath)} was empty`);
25639
+ console.error(` ${import_node_path18.default.basename(result.diagnosticLogPath)} was empty`);
25243
25640
  return;
25244
25641
  }
25245
25642
  const shown = detail.length > 4e3 ? `\u2026
25246
25643
  ${detail.slice(-4e3)}` : detail;
25247
- console.error(` ${import_node_path16.default.basename(result.diagnosticLogPath)}:`);
25644
+ console.error(` ${import_node_path18.default.basename(result.diagnosticLogPath)}:`);
25248
25645
  console.error(shown);
25249
25646
  } catch (err) {
25250
25647
  const message = err instanceof Error ? err.message : String(err);
25251
25648
  console.error(
25252
- ` failed to read ${import_node_path16.default.basename(result.diagnosticLogPath)}: ${message}`
25649
+ ` failed to read ${import_node_path18.default.basename(result.diagnosticLogPath)}: ${message}`
25253
25650
  );
25254
25651
  }
25255
25652
  }
@@ -25270,11 +25667,11 @@ async function waitForProcessExit(pid, timeoutMs = 2500) {
25270
25667
  return !isProcessAlive2(pid);
25271
25668
  }
25272
25669
  function isPlaywrightHostProcess(pid) {
25273
- const plain = (0, import_node_child_process6.spawnSync)("ps", ["-o", "command=", "-p", String(pid)], {
25670
+ const plain = (0, import_node_child_process7.spawnSync)("ps", ["-o", "command=", "-p", String(pid)], {
25274
25671
  encoding: "utf8",
25275
25672
  stdio: ["ignore", "pipe", "pipe"]
25276
25673
  });
25277
- const withEnv = (0, import_node_child_process6.spawnSync)("ps", ["eww", "-o", "command=", "-p", String(pid)], {
25674
+ const withEnv = (0, import_node_child_process7.spawnSync)("ps", ["eww", "-o", "command=", "-p", String(pid)], {
25278
25675
  encoding: "utf8",
25279
25676
  stdio: ["ignore", "pipe", "pipe"]
25280
25677
  });
@@ -25438,7 +25835,7 @@ async function runLinkCommand(args, opts = {}) {
25438
25835
  if (parsed.positional.length !== 1) {
25439
25836
  console.error(" rnx open: expected exactly one <url-or-route>");
25440
25837
  console.error(" examples: rnx open /settings | rnx open myapp://settings");
25441
- process.exit(1);
25838
+ rnxExit(1);
25442
25839
  }
25443
25840
  const bridge = createBridgeFromParsed(parsed);
25444
25841
  const simHint = parsed.simId ? ` --sim ${parsed.simId}` : "";
@@ -25452,7 +25849,7 @@ async function runLinkCommand(args, opts = {}) {
25452
25849
  warningsCommand: `rnx get warnings 5${simHint}`,
25453
25850
  requestsCommand: `rnx get requests 5${simHint}`
25454
25851
  });
25455
- process.exit(1);
25852
+ rnxExit(1);
25456
25853
  } finally {
25457
25854
  bridge.close();
25458
25855
  }
@@ -25490,11 +25887,11 @@ async function runOpenCommand(args, opts = {}) {
25490
25887
  console.error(
25491
25888
  ` rnx open: --cdp-port must be a positive port number, got "${cdpPortArg}"`
25492
25889
  );
25493
- process.exit(1);
25890
+ rnxExit(1);
25494
25891
  }
25495
25892
  if (requestedProfileId && ephemeralProfile) {
25496
25893
  console.error(" rnx open: --profile cannot be combined with --ephemeral");
25497
- process.exit(1);
25894
+ rnxExit(1);
25498
25895
  }
25499
25896
  const profileId = requestedProfileId ? ensureProfile(requestedProfileId).id : void 0;
25500
25897
  const openInNewSim = args.includes("--new") || !!profileId || ephemeralProfile;
@@ -25503,7 +25900,7 @@ async function runOpenCommand(args, opts = {}) {
25503
25900
  console.error(
25504
25901
  " rnx open: --new, --profile, and --ephemeral cannot be combined with --sim"
25505
25902
  );
25506
- process.exit(1);
25903
+ rnxExit(1);
25507
25904
  }
25508
25905
  const target = parsed.positional[0] || "";
25509
25906
  if (isAppDeepLinkTarget(target)) {
@@ -25511,7 +25908,7 @@ async function runOpenCommand(args, opts = {}) {
25511
25908
  console.error(
25512
25909
  " rnx open: deep links target the current app; remove --new, --profile, or --ephemeral"
25513
25910
  );
25514
- process.exit(1);
25911
+ rnxExit(1);
25515
25912
  }
25516
25913
  await runLinkCommand(args, opts);
25517
25914
  return;
@@ -25535,7 +25932,7 @@ async function runOpenCommand(args, opts = {}) {
25535
25932
  ` could not launch a browser: ${err instanceof Error ? err.message : String(err)}`
25536
25933
  );
25537
25934
  console.error(` open it yourself: ${target}`);
25538
- process.exit(1);
25935
+ rnxExit(1);
25539
25936
  }
25540
25937
  console.log(
25541
25938
  isLocal ? " to drive it under the CLI, point `rnx open` at the app\u2019s dev port instead (e.g. `rnx open 8081`)." : " the CLI can\u2019t drive a remote preview (the engine only attaches to the local bridge from localhost). to drive the app, run it locally and `rnx open <port>`."
@@ -25565,33 +25962,33 @@ async function runOpenCommand(args, opts = {}) {
25565
25962
  const requestedDriverId = args.find((_, i) => args[i - 1] === "--driver") || "";
25566
25963
  if (requestedDriverId && !isDriverId(requestedDriverId)) {
25567
25964
  console.error(` unknown driver "${requestedDriverId}" \u2014 run \`rnx list --drivers\``);
25568
- process.exit(1);
25965
+ rnxExit(1);
25569
25966
  }
25570
25967
  const driverId = isDriverId(requestedDriverId) ? requestedDriverId : process.env.CI === "1" || process.env.CI === "true" || process.env.GITHUB_ACTIONS === "true" || isAgentEnv() || !process.stdin.isTTY ? "playwright" : readSimulatorDriverPreference() || "playwright";
25571
25968
  const driverHeadless = resolveDriverHeadless(args, driverId);
25572
25969
  if (cdpPort && driverId !== "playwright") {
25573
25970
  console.error(" rnx open: --cdp-port is only honored by the playwright driver");
25574
- process.exit(1);
25971
+ rnxExit(1);
25575
25972
  }
25576
25973
  const hotFlag = args.includes("--hot");
25577
25974
  const noHmrFlag = args.includes("--no-hmr");
25578
25975
  if (hotFlag && noHmrFlag) {
25579
25976
  console.error(" rnx open: --hot and --no-hmr cannot be combined");
25580
- process.exit(1);
25977
+ rnxExit(1);
25581
25978
  }
25582
25979
  const hotMode = hotFlag ? true : noHmrFlag ? false : driverId !== "playwright";
25583
25980
  const deviceArg = args.find((_, i) => args[i - 1] === "--device");
25584
25981
  if (deviceArg && !(deviceArg in import_settings.devices)) {
25585
25982
  console.error(` rnx open: unknown device "${deviceArg}"`);
25586
25983
  console.error(` known devices: ${(0, import_settings.listSelectableDeviceModels)().join(", ")}`);
25587
- process.exit(1);
25984
+ rnxExit(1);
25588
25985
  }
25589
25986
  const deviceSpec = deviceArg ? import_settings.devices[deviceArg] : void 0;
25590
25987
  const viewportArg = args.find((_, i) => args[i - 1] === "--viewport");
25591
25988
  const viewportMatch = viewportArg ? /^(\d+)x(\d+)$/.exec(viewportArg) : null;
25592
25989
  if (viewportArg && !viewportMatch) {
25593
25990
  console.error(` rnx open: --viewport must be <width>x<height>, got "${viewportArg}"`);
25594
- process.exit(1);
25991
+ rnxExit(1);
25595
25992
  }
25596
25993
  const explicitViewport = viewportMatch ? { width: Number(viewportMatch[1]), height: Number(viewportMatch[2]) } : void 0;
25597
25994
  const buildShellUrlOpts = {
@@ -25684,7 +26081,7 @@ async function runOpenCommand(args, opts = {}) {
25684
26081
  warningsCommand: `rnx get warnings 5${simHint}`,
25685
26082
  requestsCommand: `rnx get requests 5${simHint}`
25686
26083
  });
25687
- process.exit(1);
26084
+ rnxExit(1);
25688
26085
  }
25689
26086
  if (!fallBackToNewSim && targetSim) {
25690
26087
  await sleep2(1500);
@@ -25695,7 +26092,7 @@ async function runOpenCommand(args, opts = {}) {
25695
26092
  );
25696
26093
  if (!ready) {
25697
26094
  console.error(" timed out waiting for current sim to load target");
25698
- process.exit(1);
26095
+ rnxExit(1);
25699
26096
  }
25700
26097
  ready.bridge.close();
25701
26098
  saveCurrentSimId(targetSim.id);
@@ -25721,13 +26118,13 @@ async function runOpenCommand(args, opts = {}) {
25721
26118
  console.error(
25722
26119
  " could not resolve a stable owning process; refusing unsupervised sim host"
25723
26120
  );
25724
- process.exit(1);
26121
+ rnxExit(1);
25725
26122
  }
25726
26123
  const simMatchesToken = (sim) => sim.url?.includes(`inspectOpen=${token}`) ?? false;
25727
26124
  const driver = getDriver(driverId);
25728
26125
  if (!driver) {
25729
26126
  console.error(` unknown driver "${driverId}" \u2014 run \`rnx list --drivers\``);
25730
- process.exit(1);
26127
+ rnxExit(1);
25731
26128
  }
25732
26129
  const connectWait = resolveDriverConnectWaitOptions();
25733
26130
  const result = await driver.launch({
@@ -25747,7 +26144,7 @@ async function runOpenCommand(args, opts = {}) {
25747
26144
  });
25748
26145
  if (!result.launched) {
25749
26146
  console.error(` ${driver.name} driver: ${result.message}`);
25750
- process.exit(1);
26147
+ rnxExit(1);
25751
26148
  }
25752
26149
  const outcome = await waitForSimMatchOrHostExit(
25753
26150
  parsed.wsPort,
@@ -25759,7 +26156,7 @@ async function runOpenCommand(args, opts = {}) {
25759
26156
  if (outcome.kind === "host-exit") {
25760
26157
  printDriverDiagnosticLogTail(result);
25761
26158
  console.error(` ${driver.name} host crashed \u2014 sim is no longer attached`);
25762
- process.exit(1);
26159
+ rnxExit(1);
25763
26160
  }
25764
26161
  if (outcome.kind === "timeout") {
25765
26162
  if (result.pid) {
@@ -25773,7 +26170,7 @@ async function runOpenCommand(args, opts = {}) {
25773
26170
  console.error(
25774
26171
  ` timed out after ${connectWait.timeoutMs}ms waiting for opened sim to connect`
25775
26172
  );
25776
- process.exit(1);
26173
+ rnxExit(1);
25777
26174
  }
25778
26175
  const match = outcome.sim;
25779
26176
  saveCurrentSimId(match.id);
@@ -25802,7 +26199,7 @@ async function runUseLikeCommand(args, opts = {}) {
25802
26199
  ` refused: ${target.id} is locked by the active user (${secs}s) \u2014 that's a live human tab`
25803
26200
  );
25804
26201
  console.error(` run \`rnx open --new\` for a fresh investigation sim`);
25805
- process.exit(1);
26202
+ rnxExit(1);
25806
26203
  }
25807
26204
  if (!force) {
25808
26205
  console.error(
@@ -25811,20 +26208,21 @@ async function runUseLikeCommand(args, opts = {}) {
25811
26208
  console.error(
25812
26209
  ` \`sootsim use ${target.id} --force\` to take it, or \`rnx open --new\` for a fresh sim`
25813
26210
  );
25814
- process.exit(1);
26211
+ rnxExit(1);
25815
26212
  }
25816
26213
  }
25817
26214
  await bridge.focusSim(target.id);
25818
26215
  saveCurrentSimId(target.id);
25819
26216
  console.log(` using: ${target.id}`);
25820
26217
  } catch (err) {
26218
+ rethrowIfExit(err);
25821
26219
  console.error(` use failed: ${err instanceof Error ? err.message : String(err)}`);
25822
26220
  await printBridgeFailureDiagnostics(bridge, {
25823
26221
  errorsCommand: `rnx get errors 5${simHint}`,
25824
26222
  warningsCommand: `rnx get warnings 5${simHint}`,
25825
26223
  requestsCommand: `rnx get requests 5${simHint}`
25826
26224
  });
25827
- process.exit(1);
26225
+ rnxExit(1);
25828
26226
  }
25829
26227
  } finally {
25830
26228
  bridge.close();
@@ -25862,10 +26260,10 @@ async function runClaimCommand(args, opts = {}) {
25862
26260
  const secs = Math.max(0, Math.round(err.lock.expiresInMs / 1e3));
25863
26261
  console.error(` claim failed: locked by ${err.lock.by} for ${secs}s more`);
25864
26262
  console.error(` use --force to take it, or \`rnx open --new\` for a fresh sim`);
25865
- process.exit(1);
26263
+ rnxExit(1);
25866
26264
  }
25867
26265
  console.error(` claim failed: ${err instanceof Error ? err.message : String(err)}`);
25868
- process.exit(1);
26266
+ rnxExit(1);
25869
26267
  }
25870
26268
  } finally {
25871
26269
  bridge.close();
@@ -25972,7 +26370,7 @@ async function runCloseCommand(args, opts = {}) {
25972
26370
  console.error(
25973
26371
  ` close failed: keep sim ${plan.missingExplicitKeepId} is not connected; not closing other sims`
25974
26372
  );
25975
- process.exit(1);
26373
+ rnxExit(1);
25976
26374
  }
25977
26375
  if (plan.targets.length === 0) {
25978
26376
  console.log(
@@ -25996,11 +26394,12 @@ async function runCloseCommand(args, opts = {}) {
25996
26394
  console.log(summary);
25997
26395
  if (result.remaining.length > 0) {
25998
26396
  console.error(` close failed: still connected: ${result.remaining.join(", ")}`);
25999
- process.exit(1);
26397
+ rnxExit(1);
26000
26398
  }
26001
26399
  } catch (err) {
26400
+ rethrowIfExit(err);
26002
26401
  console.error(` close failed: ${err instanceof Error ? err.message : String(err)}`);
26003
- process.exit(1);
26402
+ rnxExit(1);
26004
26403
  } finally {
26005
26404
  bridge.close();
26006
26405
  }
@@ -26022,7 +26421,7 @@ async function runCloseCommand(args, opts = {}) {
26022
26421
  if (!closed) {
26023
26422
  await terminatePlaywrightHostsForSims(sims, [target.id]);
26024
26423
  console.error(` close failed: ${target.id} is still connected`);
26025
- process.exit(1);
26424
+ rnxExit(1);
26026
26425
  }
26027
26426
  await terminatePlaywrightHostsForSims(sims, [target.id]);
26028
26427
  if (readCurrentSimId() === target.id) {
@@ -26031,26 +26430,27 @@ async function runCloseCommand(args, opts = {}) {
26031
26430
  }
26032
26431
  console.log(` closed: ${target.id}`);
26033
26432
  } catch (err) {
26433
+ rethrowIfExit(err);
26034
26434
  console.error(` close failed: ${err instanceof Error ? err.message : String(err)}`);
26035
26435
  await printBridgeFailureDiagnostics(bridge, {
26036
26436
  errorsCommand: `rnx get errors 5${simHint}`,
26037
26437
  warningsCommand: `rnx get warnings 5${simHint}`,
26038
26438
  requestsCommand: `rnx get requests 5${simHint}`
26039
26439
  });
26040
- process.exit(1);
26440
+ rnxExit(1);
26041
26441
  }
26042
26442
  } finally {
26043
26443
  bridge.close();
26044
26444
  }
26045
26445
  }
26046
- var import_node_child_process6, import_node_fs13, import_node_os3, import_node_path16, 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;
26446
+ var import_node_child_process7, import_node_fs15, import_node_os3, import_node_path18, 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;
26047
26447
  var init_control = __esm({
26048
26448
  "cli/commands/control.ts"() {
26049
26449
  "use strict";
26050
- import_node_child_process6 = require("node:child_process");
26051
- import_node_fs13 = __toESM(require("node:fs"), 1);
26450
+ import_node_child_process7 = require("node:child_process");
26451
+ import_node_fs15 = __toESM(require("node:fs"), 1);
26052
26452
  import_node_os3 = __toESM(require("node:os"), 1);
26053
- import_node_path16 = __toESM(require("node:path"), 1);
26453
+ import_node_path18 = __toESM(require("node:path"), 1);
26054
26454
  import_settings = require("sootsim-engine/settings");
26055
26455
  import_ws5 = require("ws");
26056
26456
  init_dev_server_scanner();
@@ -26073,6 +26473,7 @@ var init_control = __esm({
26073
26473
  init_current_sim();
26074
26474
  init_registry2();
26075
26475
  init_open_url2();
26476
+ init_run_rnx();
26076
26477
  init_ws_bridge();
26077
26478
  init_core();
26078
26479
  init_env();
@@ -26913,14 +27314,14 @@ process.stdin.on('end', async () => {
26913
27314
  function getBaseDir() {
26914
27315
  const explicit = process.env[SESSION_FILE_ENV];
26915
27316
  if (explicit?.trim()) {
26916
- return (0, import_node_path17.dirname)((0, import_node_path17.resolve)(explicit));
27317
+ return (0, import_node_path19.dirname)((0, import_node_path19.resolve)(explicit));
26917
27318
  }
26918
27319
  return electronUserDataDir();
26919
27320
  }
26920
27321
  function getSharedDesktopAuthFilePath() {
26921
27322
  const explicit = process.env[SESSION_FILE_ENV];
26922
- if (explicit?.trim()) return (0, import_node_path17.resolve)(explicit);
26923
- return (0, import_node_path17.join)(getBaseDir(), "desktop-auth.json");
27323
+ if (explicit?.trim()) return (0, import_node_path19.resolve)(explicit);
27324
+ return (0, import_node_path19.join)(getBaseDir(), "desktop-auth.json");
26924
27325
  }
26925
27326
  function normalizeUser(input) {
26926
27327
  if (!input || typeof input !== "object") return null;
@@ -26967,26 +27368,26 @@ function normalizeSession(input) {
26967
27368
  }
26968
27369
  function readSharedDesktopAuthSession() {
26969
27370
  const filepath = getSharedDesktopAuthFilePath();
26970
- if (!(0, import_node_fs14.existsSync)(filepath)) return null;
27371
+ if (!(0, import_node_fs16.existsSync)(filepath)) return null;
26971
27372
  try {
26972
- const parsed = JSON.parse((0, import_node_fs14.readFileSync)(filepath, "utf8"));
27373
+ const parsed = JSON.parse((0, import_node_fs16.readFileSync)(filepath, "utf8"));
26973
27374
  const normalized = normalizeSession(parsed);
26974
27375
  if (!normalized) {
26975
- (0, import_node_fs14.rmSync)(filepath, { force: true });
27376
+ (0, import_node_fs16.rmSync)(filepath, { force: true });
26976
27377
  return null;
26977
27378
  }
26978
27379
  return normalized;
26979
27380
  } catch {
26980
- (0, import_node_fs14.rmSync)(filepath, { force: true });
27381
+ (0, import_node_fs16.rmSync)(filepath, { force: true });
26981
27382
  return null;
26982
27383
  }
26983
27384
  }
26984
- var import_node_fs14, import_node_path17, SESSION_VERSION, SESSION_FILE_ENV, DEFAULT_ORIGIN;
27385
+ var import_node_fs16, import_node_path19, SESSION_VERSION, SESSION_FILE_ENV, DEFAULT_ORIGIN;
26985
27386
  var init_shared_session = __esm({
26986
27387
  "src/auth/shared-session.ts"() {
26987
27388
  "use strict";
26988
- import_node_fs14 = require("node:fs");
26989
- import_node_path17 = require("node:path");
27389
+ import_node_fs16 = require("node:fs");
27390
+ import_node_path19 = require("node:path");
26990
27391
  init_home_paths();
26991
27392
  SESSION_VERSION = 1;
26992
27393
  SESSION_FILE_ENV = "SOOTSIM_SHARED_AUTH_FILE";
@@ -27057,6 +27458,7 @@ var init_auth = __esm({
27057
27458
  "cli/auth.ts"() {
27058
27459
  "use strict";
27059
27460
  init_shared_session();
27461
+ init_run_rnx();
27060
27462
  GITHUB_TOKEN_PREFIXES = ["ghs_", "ghp_", "gho_", "ghu_", "github_pat_"];
27061
27463
  }
27062
27464
  });
@@ -27089,6 +27491,7 @@ var init_upload = __esm({
27089
27491
  init_auth();
27090
27492
  init_open_url2();
27091
27493
  init_run_registry();
27494
+ init_run_rnx();
27092
27495
  init_ws_bridge();
27093
27496
  REMOTE_DEFAULT_ORIGIN = process.env.SOOTSIM_UPLOAD_ORIGIN || "https://contrast.dev";
27094
27497
  REMOTE_DEFAULT_PUBLIC_PREVIEW_ORIGIN = process.env.SOOTSIM_PREVIEW_ORIGIN || rnxPublicBrand.origin;
@@ -27120,7 +27523,7 @@ async function ensureCliRecordingEntitlement(commandLabel, opts = {}) {
27120
27523
 
27121
27524
  `
27122
27525
  );
27123
- process.exit(1);
27526
+ rnxExit(1);
27124
27527
  }
27125
27528
  if (auth.kind === "github" && opts.allowGitHubAuth) return;
27126
27529
  const billingOrigin = await billingOriginFor(auth, opts.originOverride);
@@ -27137,7 +27540,7 @@ async function ensureCliRecordingEntitlement(commandLabel, opts = {}) {
27137
27540
 
27138
27541
  `
27139
27542
  );
27140
- process.exit(1);
27543
+ rnxExit(1);
27141
27544
  }
27142
27545
  if (res.status === 401) {
27143
27546
  process.stderr.write(
@@ -27147,7 +27550,7 @@ async function ensureCliRecordingEntitlement(commandLabel, opts = {}) {
27147
27550
 
27148
27551
  `
27149
27552
  );
27150
- process.exit(1);
27553
+ rnxExit(1);
27151
27554
  }
27152
27555
  if (!res.ok) {
27153
27556
  const text = await res.text().catch(() => "");
@@ -27158,7 +27561,7 @@ async function ensureCliRecordingEntitlement(commandLabel, opts = {}) {
27158
27561
 
27159
27562
  `
27160
27563
  );
27161
- process.exit(1);
27564
+ rnxExit(1);
27162
27565
  }
27163
27566
  const data = await res.json();
27164
27567
  if (data.entitlements?.desktopRecording === true) return;
@@ -27171,7 +27574,7 @@ async function ensureCliRecordingEntitlement(commandLabel, opts = {}) {
27171
27574
 
27172
27575
  `
27173
27576
  );
27174
- process.exit(1);
27577
+ rnxExit(1);
27175
27578
  }
27176
27579
  var init_recording_access = __esm({
27177
27580
  "cli/recording-access.ts"() {
@@ -27179,6 +27582,7 @@ var init_recording_access = __esm({
27179
27582
  init_shared_session();
27180
27583
  init_auth();
27181
27584
  init_upload();
27585
+ init_run_rnx();
27182
27586
  }
27183
27587
  });
27184
27588
 
@@ -27225,16 +27629,16 @@ function resolveFlowScreenshotPath(screenshotDir, name2) {
27225
27629
  }
27226
27630
  function resolveProjectRelativeFlowBase(flowDir) {
27227
27631
  if (!flowDir) return process.cwd();
27228
- return path16.basename(flowDir) === ".maestro" ? path16.dirname(flowDir) : flowDir;
27632
+ return path18.basename(flowDir) === ".maestro" ? path18.dirname(flowDir) : flowDir;
27229
27633
  }
27230
27634
  function resolveFlowScreenshotPathWithMode(screenshotDir, name2, opts) {
27231
27635
  const normalizedName = name2.endsWith(".png") ? name2 : `${name2}.png`;
27232
- if (path16.isAbsolute(normalizedName)) return normalizedName;
27636
+ if (path18.isAbsolute(normalizedName)) return normalizedName;
27233
27637
  if (opts.mode === "flow" && /[\\/]/.test(normalizedName)) {
27234
27638
  const baseDir = normalizedName.startsWith("./") || normalizedName.startsWith("../") ? opts.flowDir ?? process.cwd() : resolveProjectRelativeFlowBase(opts.flowDir);
27235
- return path16.resolve(baseDir, normalizedName);
27639
+ return path18.resolve(baseDir, normalizedName);
27236
27640
  }
27237
- return path16.join(screenshotDir, normalizedName);
27641
+ return path18.join(screenshotDir, normalizedName);
27238
27642
  }
27239
27643
  function normalizeFlowScreenshotSpec(step) {
27240
27644
  if (typeof step === "string") {
@@ -27291,12 +27695,12 @@ function parseFlowSteps(content) {
27291
27695
  const parsed = import_yaml.default.parse(candidate);
27292
27696
  return Array.isArray(parsed) ? parsed : [];
27293
27697
  }
27294
- var fs16, path16, import_settings3, import_yaml, import_proof_frame, DEFAULT_TIMEOUT, FLOW_TIMEOUT_SCALE, scaledTimeout, SCREEN_W, SCREEN_H, SootSimBridgeFlowRunner;
27698
+ var fs18, path18, import_settings3, import_yaml, import_proof_frame, DEFAULT_TIMEOUT, FLOW_TIMEOUT_SCALE, scaledTimeout, SCREEN_W, SCREEN_H, SootSimBridgeFlowRunner;
27295
27699
  var init_bridge_flow_runner = __esm({
27296
27700
  "cli/bridge-flow-runner.ts"() {
27297
27701
  "use strict";
27298
- fs16 = __toESM(require("fs"), 1);
27299
- path16 = __toESM(require("path"), 1);
27702
+ fs18 = __toESM(require("fs"), 1);
27703
+ path18 = __toESM(require("path"), 1);
27300
27704
  import_settings3 = require("sootsim-engine/settings");
27301
27705
  import_yaml = __toESM(require("yaml"), 1);
27302
27706
  import_proof_frame = __toESM(require_proof_frame(), 1);
@@ -27503,14 +27907,14 @@ var init_bridge_flow_runner = __esm({
27503
27907
  if (chunks.length === 0) {
27504
27908
  throw new Error("recording requested but no video buffer was produced");
27505
27909
  }
27506
- fs16.mkdirSync(this.opts.recordingOutputDir, { recursive: true });
27910
+ fs18.mkdirSync(this.opts.recordingOutputDir, { recursive: true });
27507
27911
  const videoMime = stopped.mime || this.opts.recordingFormat || "video/webm";
27508
27912
  const extension = videoMime.includes("mp4") ? "mp4" : "webm";
27509
- const outputPath = path16.join(
27913
+ const outputPath = path18.join(
27510
27914
  this.opts.recordingOutputDir,
27511
27915
  `sootsim-${Date.now()}.${extension}`
27512
27916
  );
27513
- fs16.writeFileSync(outputPath, Buffer.concat(chunks));
27917
+ fs18.writeFileSync(outputPath, Buffer.concat(chunks));
27514
27918
  return outputPath;
27515
27919
  }
27516
27920
  async waitForRecordingTail(opts) {
@@ -28006,7 +28410,7 @@ var init_bridge_flow_runner = __esm({
28006
28410
  flowDir: this.opts.flowDir
28007
28411
  }
28008
28412
  );
28009
- fs16.mkdirSync(path16.dirname(outputPath), { recursive: true });
28413
+ fs18.mkdirSync(path18.dirname(outputPath), { recursive: true });
28010
28414
  const layers = spec.layers ?? this.opts.screenshotLayers;
28011
28415
  const screenshotRequest = {
28012
28416
  type: "screenshot",
@@ -28045,11 +28449,11 @@ var init_bridge_flow_runner = __esm({
28045
28449
  throw new Error("could not read current device model for framed screenshot");
28046
28450
  }
28047
28451
  const framed = await composeFramedScreenshot(rawBuffer, model);
28048
- fs16.writeFileSync(outputPath, framed);
28452
+ fs18.writeFileSync(outputPath, framed);
28049
28453
  console.log(`[flow] screenshot: ${outputPath} (frame: ${model})`);
28050
28454
  return outputPath;
28051
28455
  }
28052
- fs16.writeFileSync(outputPath, rawBuffer);
28456
+ fs18.writeFileSync(outputPath, rawBuffer);
28053
28457
  console.log(`[flow] screenshot: ${outputPath}`);
28054
28458
  return outputPath;
28055
28459
  }
@@ -28071,10 +28475,10 @@ var init_bridge_flow_runner = __esm({
28071
28475
  // + a11y tree + console errors/warnings + failed network requests +
28072
28476
  // shell state, all under one directory so post-mortem is one grep away.
28073
28477
  async captureFailureBundle(outputDir, context) {
28074
- fs16.mkdirSync(outputDir, { recursive: true });
28478
+ fs18.mkdirSync(outputDir, { recursive: true });
28075
28479
  const write = (name2, content) => {
28076
28480
  try {
28077
- fs16.writeFileSync(path16.join(outputDir, name2), content);
28481
+ fs18.writeFileSync(path18.join(outputDir, name2), content);
28078
28482
  } catch {
28079
28483
  }
28080
28484
  };
@@ -28612,8 +29016,8 @@ var init_bridge_flow_runner = __esm({
28612
29016
  runScript(target) {
28613
29017
  const spec = typeof target === "string" ? { file: target } : target;
28614
29018
  if (!spec.file) throw new Error("runScript requires a file");
28615
- const scriptPath = path16.resolve(this.opts.flowDir, spec.file);
28616
- const source = fs16.readFileSync(scriptPath, "utf8");
29019
+ const scriptPath = path18.resolve(this.opts.flowDir, spec.file);
29020
+ const source = fs18.readFileSync(scriptPath, "utf8");
28617
29021
  this.js.evaluate(source, { env: spec.env });
28618
29022
  console.log(`[flow] runScript: ${spec.file} done`);
28619
29023
  }
@@ -28910,10 +29314,10 @@ var init_bridge_flow_runner = __esm({
28910
29314
  for (const [k, v] of Object.entries(env)) this.js.putEnv(k, v);
28911
29315
  }
28912
29316
  if (file) {
28913
- const flowPath = path16.resolve(this.opts.flowDir, file);
28914
- const flowContent = fs16.readFileSync(flowPath, "utf8");
29317
+ const flowPath = path18.resolve(this.opts.flowDir, file);
29318
+ const flowContent = fs18.readFileSync(flowPath, "utf8");
28915
29319
  const previousDir = this.opts.flowDir;
28916
- this.opts.flowDir = path16.dirname(flowPath);
29320
+ this.opts.flowDir = path18.dirname(flowPath);
28917
29321
  try {
28918
29322
  await this.runFlow(parseFlowSteps(flowContent));
28919
29323
  } finally {
@@ -29068,8 +29472,8 @@ __export(registry_exports, {
29068
29472
  skillRegistry: () => skillRegistry
29069
29473
  });
29070
29474
  module.exports = __toCommonJS(registry_exports);
29071
- var fs17 = __toESM(require("fs"), 1);
29072
- var path17 = __toESM(require("path"), 1);
29475
+ var fs19 = __toESM(require("fs"), 1);
29476
+ var path19 = __toESM(require("path"), 1);
29073
29477
 
29074
29478
  // src/skills/builtin/a11y-review.ts
29075
29479
  init_reap_browser();
@@ -29088,8 +29492,8 @@ var skill = {
29088
29492
  },
29089
29493
  async execute(params, context) {
29090
29494
  const { chromium } = await import("playwright");
29091
- const path18 = await import("path");
29092
- const fs18 = await import("fs");
29495
+ const path20 = await import("path");
29496
+ const fs20 = await import("fs");
29093
29497
  const browser = await launchReapedChrome((options) => chromium.launch(options), {
29094
29498
  headless: true
29095
29499
  });
@@ -29144,9 +29548,9 @@ var skill = {
29144
29548
  warnings: issues.filter((i) => i.severity === "warning").length,
29145
29549
  tree: typeof tree === "string" ? tree : JSON.stringify(tree)
29146
29550
  };
29147
- const reportPath = path18.join(context.outputDir, "a11y-report.json");
29148
- fs18.mkdirSync(path18.dirname(reportPath), { recursive: true });
29149
- fs18.writeFileSync(reportPath, JSON.stringify(report, null, 2));
29551
+ const reportPath = path20.join(context.outputDir, "a11y-report.json");
29552
+ fs20.mkdirSync(path20.dirname(reportPath), { recursive: true });
29553
+ fs20.writeFileSync(reportPath, JSON.stringify(report, null, 2));
29150
29554
  return {
29151
29555
  success: issues.filter((i) => i.severity === "error").length === 0,
29152
29556
  message: `${report.errors} errors, ${report.warnings} warnings across ${report.nodeCount} nodes`,
@@ -29179,14 +29583,14 @@ var skill2 = {
29179
29583
  }
29180
29584
  },
29181
29585
  async execute(params, context) {
29182
- const fs18 = await import("node:fs");
29183
- const path18 = await import("node:path");
29586
+ const fs20 = await import("node:fs");
29587
+ const path20 = await import("node:path");
29184
29588
  const { COMPAT_RESULT_CAVEAT: COMPAT_RESULT_CAVEAT2, formatPackageStatus: formatPackageStatus2, formatScanCounts: formatScanCounts2, scanDeps: scanDeps2 } = await Promise.resolve().then(() => (init_web(), web_exports));
29185
- const packagePath = path18.join(context.projectDir, "package.json");
29186
- if (!fs18.existsSync(packagePath)) {
29589
+ const packagePath = path20.join(context.projectDir, "package.json");
29590
+ if (!fs20.existsSync(packagePath)) {
29187
29591
  return { success: false, message: "No package.json found" };
29188
29592
  }
29189
- const parsed = JSON.parse(fs18.readFileSync(packagePath, "utf8"));
29593
+ const parsed = JSON.parse(fs20.readFileSync(packagePath, "utf8"));
29190
29594
  if (!parsed || typeof parsed !== "object") {
29191
29595
  return { success: false, message: "package.json is not an object" };
29192
29596
  }
@@ -29198,9 +29602,9 @@ var skill2 = {
29198
29602
  }) : scanDeps2(parsed);
29199
29603
  const packageResult = params.packageName ? scan.packages[0] : void 0;
29200
29604
  const message = packageResult ? `${params.packageName}: ${formatPackageStatus2(packageResult)}. ${COMPAT_RESULT_CAVEAT2}` : `${formatScanCounts2(scan)}. ${COMPAT_RESULT_CAVEAT2}`;
29201
- const reportPath = path18.join(context.outputDir, "compat-report.json");
29202
- fs18.mkdirSync(path18.dirname(reportPath), { recursive: true });
29203
- fs18.writeFileSync(reportPath, `${JSON.stringify(scan, null, 2)}
29605
+ const reportPath = path20.join(context.outputDir, "compat-report.json");
29606
+ fs20.mkdirSync(path20.dirname(reportPath), { recursive: true });
29607
+ fs20.writeFileSync(reportPath, `${JSON.stringify(scan, null, 2)}
29204
29608
  `);
29205
29609
  return {
29206
29610
  success: true,
@@ -29244,8 +29648,8 @@ var skill3 = {
29244
29648
  record: { type: "boolean", description: "Record video of the flow" }
29245
29649
  },
29246
29650
  async execute(params, context) {
29247
- const fs18 = await import("fs");
29248
- const path18 = await import("path");
29651
+ const fs20 = await import("fs");
29652
+ const path20 = await import("path");
29249
29653
  const yaml2 = await import("yaml");
29250
29654
  const { DEFAULT_SOOTSIM_BRIDGE_PORT: DEFAULT_SOOTSIM_BRIDGE_PORT2 } = await Promise.resolve().then(() => (init_bridge_constants(), bridge_constants_exports));
29251
29655
  const { SootSimBridgeHost: SootSimBridgeHost2 } = await Promise.resolve().then(() => (init_bridge_host(), bridge_host_exports));
@@ -29255,7 +29659,7 @@ var skill3 = {
29255
29659
  const host = new SootSimBridgeHost2({ port: DEFAULT_SOOTSIM_BRIDGE_PORT2 });
29256
29660
  host.start({ silent: true });
29257
29661
  try {
29258
- const flowContent = fs18.readFileSync(params.flowPath, "utf8");
29662
+ const flowContent = fs20.readFileSync(params.flowPath, "utf8");
29259
29663
  const fmMatch = flowContent.match(/^---\n([\s\S]*?)\n---\n?/);
29260
29664
  const stepsBody = fmMatch ? flowContent.slice(fmMatch[0].length) : flowContent;
29261
29665
  const steps = yaml2.parse(stepsBody);
@@ -29282,7 +29686,7 @@ var skill3 = {
29282
29686
  });
29283
29687
  const driver = new SootSimBridgeFlowRunner2(bridge, {
29284
29688
  screenshotDir: context.outputDir,
29285
- flowDir: path18.dirname(path18.resolve(params.flowPath)),
29689
+ flowDir: path20.dirname(path20.resolve(params.flowPath)),
29286
29690
  simId,
29287
29691
  recordingOutputDir: params.record ? context.outputDir : void 0
29288
29692
  });
@@ -29304,7 +29708,7 @@ var skill3 = {
29304
29708
  simId,
29305
29709
  videoPath: videoPath || void 0
29306
29710
  },
29307
- artifacts: videoPath ? [{ type: "video", name: path18.basename(videoPath), path: videoPath }] : void 0
29711
+ artifacts: videoPath ? [{ type: "video", name: path20.basename(videoPath), path: videoPath }] : void 0
29308
29712
  };
29309
29713
  } finally {
29310
29714
  try {
@@ -29351,8 +29755,8 @@ var skill4 = {
29351
29755
  },
29352
29756
  async execute(params, context) {
29353
29757
  const { chromium } = await import("playwright");
29354
- const path18 = await import("path");
29355
- const fs18 = await import("fs");
29758
+ const path20 = await import("path");
29759
+ const fs20 = await import("fs");
29356
29760
  const browser = await launchReapedChrome((options) => chromium.launch(options), {
29357
29761
  headless: true
29358
29762
  });
@@ -29378,9 +29782,9 @@ var skill4 = {
29378
29782
  ...profile,
29379
29783
  fps: syncCount > 0 ? (syncCount / (duration / 1e3)).toFixed(1) : "0"
29380
29784
  };
29381
- const reportPath = path18.join(context.outputDir, "perf-report.json");
29382
- fs18.mkdirSync(path18.dirname(reportPath), { recursive: true });
29383
- fs18.writeFileSync(reportPath, JSON.stringify(report, null, 2));
29785
+ const reportPath = path20.join(context.outputDir, "perf-report.json");
29786
+ fs20.mkdirSync(path20.dirname(reportPath), { recursive: true });
29787
+ fs20.writeFileSync(reportPath, JSON.stringify(report, null, 2));
29384
29788
  return {
29385
29789
  success: true,
29386
29790
  message: `${report.fps} fps, ${profile.avgSyncMs?.toFixed(1)}ms avg sync, ${nodeCount} nodes`,
@@ -29413,8 +29817,8 @@ var skill5 = {
29413
29817
  },
29414
29818
  async execute(params, context) {
29415
29819
  const { chromium } = await import("playwright");
29416
- const path18 = await import("path");
29417
- const fs18 = await import("fs");
29820
+ const path20 = await import("path");
29821
+ const fs20 = await import("fs");
29418
29822
  const browser = await launchReapedChrome((options) => chromium.launch(options), {
29419
29823
  headless: true
29420
29824
  });
@@ -29422,8 +29826,8 @@ var skill5 = {
29422
29826
  try {
29423
29827
  await page.goto(context.url, { waitUntil: "networkidle" });
29424
29828
  await page.waitForTimeout(2e3);
29425
- const outputPath = path18.join(context.outputDir, `${params.name}.png`);
29426
- fs18.mkdirSync(path18.dirname(outputPath), { recursive: true });
29829
+ const outputPath = path20.join(context.outputDir, `${params.name}.png`);
29830
+ fs20.mkdirSync(path20.dirname(outputPath), { recursive: true });
29427
29831
  await page.screenshot({ path: outputPath });
29428
29832
  return {
29429
29833
  success: true,
@@ -29464,8 +29868,8 @@ var skill6 = {
29464
29868
  },
29465
29869
  async execute(params, context) {
29466
29870
  const { chromium } = await import("playwright");
29467
- const path18 = await import("path");
29468
- const fs18 = await import("fs");
29871
+ const path20 = await import("path");
29872
+ const fs20 = await import("fs");
29469
29873
  const browser = await launchReapedChrome((options) => chromium.launch(options), {
29470
29874
  headless: true
29471
29875
  });
@@ -29473,14 +29877,14 @@ var skill6 = {
29473
29877
  try {
29474
29878
  await page.goto(context.url, { waitUntil: "networkidle" });
29475
29879
  await page.waitForTimeout(2e3);
29476
- const currentPath = path18.join(context.outputDir, `${params.name}-current.png`);
29477
- fs18.mkdirSync(path18.dirname(currentPath), { recursive: true });
29880
+ const currentPath = path20.join(context.outputDir, `${params.name}-current.png`);
29881
+ fs20.mkdirSync(path20.dirname(currentPath), { recursive: true });
29478
29882
  await page.screenshot({ path: currentPath });
29479
29883
  try {
29480
29884
  const { PNG } = await import("pngjs");
29481
29885
  const pixelmatch = (await import("pixelmatch")).default;
29482
- const baseline = PNG.sync.read(fs18.readFileSync(params.baseline));
29483
- const current = PNG.sync.read(fs18.readFileSync(currentPath));
29886
+ const baseline = PNG.sync.read(fs20.readFileSync(params.baseline));
29887
+ const current = PNG.sync.read(fs20.readFileSync(currentPath));
29484
29888
  const { width, height } = baseline;
29485
29889
  const diff = new PNG({ width, height });
29486
29890
  const numDiffPixels = pixelmatch(
@@ -29491,8 +29895,8 @@ var skill6 = {
29491
29895
  height,
29492
29896
  { threshold: params.threshold || 0.1 }
29493
29897
  );
29494
- const diffPath = path18.join(context.outputDir, `${params.name}-diff.png`);
29495
- fs18.writeFileSync(diffPath, PNG.sync.write(diff));
29898
+ const diffPath = path20.join(context.outputDir, `${params.name}-diff.png`);
29899
+ fs20.writeFileSync(diffPath, PNG.sync.write(diff));
29496
29900
  const totalPixels = width * height;
29497
29901
  const diffPercent = (numDiffPixels / totalPixels * 100).toFixed(2);
29498
29902
  return {
@@ -29556,10 +29960,10 @@ var skillRegistry = {
29556
29960
  },
29557
29961
  // load skills from a project manifest (sootsim-skills.json)
29558
29962
  async loadFromManifest(projectDir) {
29559
- const manifestPath = path17.join(projectDir, "sootsim-skills.json");
29560
- if (!fs17.existsSync(manifestPath)) return;
29963
+ const manifestPath = path19.join(projectDir, "sootsim-skills.json");
29964
+ if (!fs19.existsSync(manifestPath)) return;
29561
29965
  try {
29562
- const manifest = JSON.parse(fs17.readFileSync(manifestPath, "utf8"));
29966
+ const manifest = JSON.parse(fs19.readFileSync(manifestPath, "utf8"));
29563
29967
  if (Array.isArray(manifest.skills)) {
29564
29968
  for (const entry of manifest.skills) {
29565
29969
  if (typeof entry === "string") {
@@ -29570,7 +29974,7 @@ var skillRegistry = {
29570
29974
  }
29571
29975
  } else if (entry.file) {
29572
29976
  try {
29573
- const mod = await import(path17.resolve(projectDir, entry.file));
29977
+ const mod = await import(path19.resolve(projectDir, entry.file));
29574
29978
  if (mod.skill) this.register(mod.skill);
29575
29979
  } catch {
29576
29980
  }
@@ -29583,10 +29987,10 @@ var skillRegistry = {
29583
29987
  // auto-discover skills based on project dependencies
29584
29988
  suggestSkills(projectDir) {
29585
29989
  const suggestions = [];
29586
- const pkgPath = path17.join(projectDir, "package.json");
29587
- if (!fs17.existsSync(pkgPath)) return suggestions;
29990
+ const pkgPath = path19.join(projectDir, "package.json");
29991
+ if (!fs19.existsSync(pkgPath)) return suggestions;
29588
29992
  try {
29589
- const pkg = JSON.parse(fs17.readFileSync(pkgPath, "utf8"));
29993
+ const pkg = JSON.parse(fs19.readFileSync(pkgPath, "utf8"));
29590
29994
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
29591
29995
  if (deps["detox"] || deps["maestro"]) {
29592
29996
  suggestions.push("maestro-test");