rnxsim 0.1.370 → 0.1.372

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 (207) hide show
  1. package/cli/bin.ts +2 -2
  2. package/cli/commands/box/checkout-plane.ts +65 -14
  3. package/cli/commands/box/cloud-box.ts +294 -0
  4. package/cli/commands/box/plane-share.ts +208 -0
  5. package/cli/commands/box/rnx-command.ts +2 -2
  6. package/cli/commands/box/search-command.ts +157 -0
  7. package/cli/commands/box.ts +269 -13
  8. package/cli/commands/control.ts +6 -1
  9. package/cli/outbound-endpoints.ts +37 -1
  10. package/dist-cli/bin.js +5 -5
  11. package/dist-cli/chunks/{agent-3R4424LW.js → agent-RTAKJ5UM.js} +2 -2
  12. package/dist-cli/chunks/{agent-wrapper-4PPWZWSY.js → agent-wrapper-JWRDI26D.js} +2 -2
  13. package/dist-cli/chunks/{app-fonts-KYXB6CXQ.js → app-fonts-RKO3N4PB.js} +2 -2
  14. package/dist-cli/chunks/{assert-7EZOWGYQ.js → assert-Y5LXPWC6.js} +2 -2
  15. package/dist-cli/chunks/{auth-L7M36H6C.js → auth-4SPTRWTM.js} +2 -2
  16. package/dist-cli/chunks/{beta-FKAF6YWJ.js → beta-KYS22QPY.js} +2 -2
  17. package/dist-cli/chunks/{box-5Y5VBMHV.js → box-7CK72ZPF.js} +617 -596
  18. package/dist-cli/chunks/{camera-XHF2RPFG.js → camera-RUL426MQ.js} +2 -2
  19. package/dist-cli/chunks/{chunk-EQ3OE2AV.js → chunk-2MA3Q44D.js} +1 -1
  20. package/dist-cli/chunks/{chunk-DHYTRCIP.js → chunk-2OA2WE7G.js} +2 -2
  21. package/dist-cli/chunks/{chunk-DMHSD6YJ.js → chunk-3C6LTIDV.js} +2 -2
  22. package/dist-cli/chunks/{chunk-NR2V2ONN.js → chunk-4UMODU6C.js} +2 -2
  23. package/dist-cli/chunks/{chunk-Z2VBSNFI.js → chunk-5Q6ZEZFF.js} +1 -1
  24. package/dist-cli/chunks/{chunk-LUOHRBJG.js → chunk-7FYOZQBM.js} +1 -1
  25. package/dist-cli/chunks/{chunk-Q4MRJ6PO.js → chunk-BRRWRBW7.js} +2 -2
  26. package/dist-cli/chunks/chunk-CEQXZ3GA.js +11 -0
  27. package/dist-cli/chunks/{chunk-DUXHYPBP.js → chunk-CZ2FOZVC.js} +2 -2
  28. package/dist-cli/chunks/{chunk-CMVRTZ5G.js → chunk-D5AT7UT5.js} +2 -2
  29. package/dist-cli/chunks/{chunk-D2GZASRV.js → chunk-DQOQQFMP.js} +1 -1
  30. package/dist-cli/chunks/{chunk-3UXXYJVD.js → chunk-EKSSAC4L.js} +1 -1
  31. package/dist-cli/chunks/{chunk-5BR33NAZ.js → chunk-EY5OHL2M.js} +1 -1
  32. package/dist-cli/chunks/{chunk-FK4JSTZ3.js → chunk-FOW2LPM7.js} +2 -2
  33. package/dist-cli/chunks/{chunk-WFQHT4QD.js → chunk-G5G7PQKO.js} +2 -2
  34. package/dist-cli/chunks/{chunk-WPV4H5S4.js → chunk-GPYG2L7R.js} +2 -2
  35. package/dist-cli/chunks/{chunk-3SOPYG7S.js → chunk-H2SKPUSI.js} +2 -2
  36. package/dist-cli/chunks/{chunk-6A6L3VJR.js → chunk-H567W7JT.js} +2 -2
  37. package/dist-cli/chunks/{chunk-PAUEZO6F.js → chunk-HDGZAVH3.js} +2 -2
  38. package/dist-cli/chunks/{chunk-AALKIAUZ.js → chunk-I37LRPZ3.js} +1 -1
  39. package/dist-cli/chunks/{chunk-JZLDHPB7.js → chunk-I7LRVN5M.js} +2 -2
  40. package/dist-cli/chunks/{chunk-3BG6UCQY.js → chunk-IX25FFF3.js} +11 -11
  41. package/dist-cli/chunks/{chunk-72S5FCBY.js → chunk-J2TJRPDB.js} +2 -2
  42. package/dist-cli/chunks/{chunk-RRX52O37.js → chunk-JPRHJMUJ.js} +1 -1
  43. package/dist-cli/chunks/{chunk-BMIJZNKD.js → chunk-K4TGUDY4.js} +2 -2
  44. package/dist-cli/chunks/{chunk-7C7TJYZ3.js → chunk-KSE5XGYU.js} +2 -2
  45. package/dist-cli/chunks/{chunk-OJZHECGH.js → chunk-KVVNCIJA.js} +1 -1
  46. package/dist-cli/chunks/{chunk-LXPHL6FO.js → chunk-KWLUOLYQ.js} +2 -2
  47. package/dist-cli/chunks/{chunk-C7BA5CCM.js → chunk-KXAE64HF.js} +2 -2
  48. package/dist-cli/chunks/{chunk-EWSWOWXK.js → chunk-L5BEWXQZ.js} +2 -2
  49. package/dist-cli/chunks/{chunk-D24EOJ4C.js → chunk-LAQKTHRM.js} +2 -2
  50. package/dist-cli/chunks/{chunk-IDLFVRPN.js → chunk-LWLHVF6Y.js} +1 -1
  51. package/dist-cli/chunks/{chunk-EOIBLKZO.js → chunk-MKHW3JNL.js} +2 -2
  52. package/dist-cli/chunks/{chunk-64JAEJ6N.js → chunk-MSM2E74F.js} +1 -1
  53. package/dist-cli/chunks/{chunk-DXV37CJN.js → chunk-NBEOA72C.js} +1 -1
  54. package/dist-cli/chunks/{chunk-H5X5FJ63.js → chunk-NLMDMNO6.js} +2 -2
  55. package/dist-cli/chunks/{chunk-6W4HIWC7.js → chunk-NT43X5LI.js} +2 -2
  56. package/dist-cli/chunks/{chunk-PNOENVMJ.js → chunk-NWPYM54M.js} +1 -1
  57. package/dist-cli/chunks/{chunk-O6QLKSAM.js → chunk-OLHHB6LD.js} +2 -2
  58. package/dist-cli/chunks/{chunk-MDCY532E.js → chunk-P6K6XFWE.js} +2 -2
  59. package/dist-cli/chunks/{chunk-LIMWG54N.js → chunk-PETP6LVI.js} +1 -1
  60. package/dist-cli/chunks/{chunk-YIZGNT7A.js → chunk-PKY76S57.js} +1 -1
  61. package/dist-cli/chunks/chunk-POR7R3TP.js +4 -0
  62. package/dist-cli/chunks/chunk-PUH6RQBJ.js +7 -0
  63. package/dist-cli/chunks/{chunk-NJMU5BN6.js → chunk-Q5WPPOFV.js} +1 -1
  64. package/dist-cli/chunks/{chunk-HQQTS2CB.js → chunk-Q7JSU5DI.js} +3 -3
  65. package/dist-cli/chunks/{chunk-U63T6LIQ.js → chunk-QA2PTSOC.js} +1 -1
  66. package/dist-cli/chunks/{chunk-2FOGXVOK.js → chunk-R7HMFLXI.js} +2 -2
  67. package/dist-cli/chunks/{chunk-FA75F34Z.js → chunk-RXKZJAGY.js} +1 -1
  68. package/dist-cli/chunks/{chunk-5RKRVS5A.js → chunk-SC7PGULZ.js} +2 -2
  69. package/dist-cli/chunks/{chunk-UFVAAWGZ.js → chunk-SLTEYNAE.js} +1 -1
  70. package/dist-cli/chunks/{chunk-6GPS7U4X.js → chunk-SSQCKJQZ.js} +2 -2
  71. package/dist-cli/chunks/{chunk-ARMXC3C3.js → chunk-SZX6ARJX.js} +3 -3
  72. package/dist-cli/chunks/{chunk-6S5LJV2O.js → chunk-U3XYGLXG.js} +2 -2
  73. package/dist-cli/chunks/chunk-UA5WQCBU.js +7 -0
  74. package/dist-cli/chunks/{chunk-VVQCZ42C.js → chunk-UVKX6FQ4.js} +1 -1
  75. package/dist-cli/chunks/{chunk-Z3DENGXW.js → chunk-VQQXUXXF.js} +2 -2
  76. package/dist-cli/chunks/{chunk-I52NT6VG.js → chunk-WR7LRCYW.js} +1 -1
  77. package/dist-cli/chunks/{chunk-HEKHLQ3B.js → chunk-XA4YU3A4.js} +3 -3
  78. package/dist-cli/chunks/{chunk-GRO74J2X.js → chunk-YEGWC2OY.js} +1 -1
  79. package/dist-cli/chunks/{chunk-AVJBT5LF.js → chunk-YWTBN7SD.js} +1 -1
  80. package/dist-cli/chunks/{chunk-PIZXOXBL.js → chunk-ZRQKEKHX.js} +2 -2
  81. package/dist-cli/chunks/{chunk-X6WQ4YWE.js → chunk-ZTZTRD5R.js} +1 -1
  82. package/dist-cli/chunks/{cleanup-MXMMJ7JW.js → cleanup-7ATBNZHC.js} +2 -2
  83. package/dist-cli/chunks/cli-version-D2IDQO7X.js +4 -0
  84. package/dist-cli/chunks/{compat-E6KE5KDI.js → compat-4ZC6ZMMA.js} +2 -2
  85. package/dist-cli/chunks/{config-QSLLVZDS.js → config-EOBODQ32.js} +2 -2
  86. package/dist-cli/chunks/{connect-QE4VU6GB.js → connect-ZQ72CZ7O.js} +2 -2
  87. package/dist-cli/chunks/control-FKPKLOE2.js +4 -0
  88. package/dist-cli/chunks/daemon-WFHWS3JU.js +4 -0
  89. package/dist-cli/chunks/{debug-5FG3MBJS.js → debug-7Z325GNU.js} +3 -3
  90. package/dist-cli/chunks/{desktop-5EZX7NX5.js → desktop-CR7ALX7H.js} +3 -3
  91. package/dist-cli/chunks/{detox-WJGQP5TM.js → detox-RZ37736M.js} +2 -2
  92. package/dist-cli/chunks/{device-D7LKCAMS.js → device-TA3SQP6K.js} +2 -2
  93. package/dist-cli/chunks/{diagnose-A3W6ZTGA.js → diagnose-JFKFKR3S.js} +2 -2
  94. package/dist-cli/chunks/disk-cleanup-HZM7VM4S.js +4 -0
  95. package/dist-cli/chunks/{do-chain-RUJNRPPF.js → do-chain-MTT65YNX.js} +2 -2
  96. package/dist-cli/chunks/drivers-BXEXWRVJ.js +4 -0
  97. package/dist-cli/chunks/{env-U67SQVTV.js → env-SFI24PLS.js} +2 -2
  98. package/dist-cli/chunks/{film-XC6UAQ3V.js → film-GPHSJLIH.js} +3 -3
  99. package/dist-cli/chunks/flow-FBCU3TZN.js +4 -0
  100. package/dist-cli/chunks/help-6MLROWYO.js +4 -0
  101. package/dist-cli/chunks/{hidden-runtime-alias-7S2TNZXG.js → hidden-runtime-alias-7Z6PYH53.js} +2 -2
  102. package/dist-cli/chunks/{home-paths-LMO5LB4F.js → home-paths-MEGVA6ET.js} +2 -2
  103. package/dist-cli/chunks/inspect-EWT5SXBD.js +4 -0
  104. package/dist-cli/chunks/install-desktop-IFBUH7MH.js +4 -0
  105. package/dist-cli/chunks/{login-JIU42S4C.js → login-EHUZYRAM.js} +2 -2
  106. package/dist-cli/chunks/{logout-JTAWQXZO.js → logout-WPGT62N6.js} +2 -2
  107. package/dist-cli/chunks/{maestro-TYBQYOGB.js → maestro-4HVDLCTI.js} +3 -3
  108. package/dist-cli/chunks/{maestro-generate-YLAGWVKE.js → maestro-generate-VBS2EZMM.js} +3 -3
  109. package/dist-cli/chunks/{mode-DV24ZRZ6.js → mode-MGTS4QTF.js} +2 -2
  110. package/dist-cli/chunks/{optional-demo-registry-JZH66VIM.js → optional-demo-registry-F3FQT5WT.js} +2 -2
  111. package/dist-cli/chunks/{perf-LXXJ4EAX.js → perf-47R3OEI7.js} +2 -2
  112. package/dist-cli/chunks/{permissions-KAOVZUPW.js → permissions-ZOLFZWR2.js} +2 -2
  113. package/dist-cli/chunks/{platform-KW35446W.js → platform-C4E26ZBZ.js} +3 -3
  114. package/dist-cli/chunks/privacy-WXGGG4ZI.js +4 -0
  115. package/dist-cli/chunks/{record-EK3ETFHZ.js → record-OUXWCWUF.js} +3 -3
  116. package/dist-cli/chunks/{report-issue-NW5PCAZF.js → report-issue-LJJXONXQ.js} +2 -2
  117. package/dist-cli/chunks/reset-ZZYLPETS.js +4 -0
  118. package/dist-cli/chunks/resolve-target-MVZZR2JW.js +4 -0
  119. package/dist-cli/chunks/runtime-DEC5AF3M.js +4 -0
  120. package/dist-cli/chunks/{screenshot-command-TQSPTFT2.js → screenshot-command-CUHDOS5D.js} +3 -3
  121. package/dist-cli/chunks/{screenshot-layers-IZP3L3AQ.js → screenshot-layers-DVWQVKT6.js} +2 -2
  122. package/dist-cli/chunks/{screenshots-capture-K7LJOVAE.js → screenshots-capture-GWIUJ3A6.js} +2 -2
  123. package/dist-cli/chunks/{serve-MMX4WXXG.js → serve-TX5OKGJM.js} +19 -19
  124. package/dist-cli/chunks/setup-X7LLGW67.js +4 -0
  125. package/dist-cli/chunks/{sim-client-KLAYIFOC.js → sim-client-W4WZOZ3P.js} +2 -2
  126. package/dist-cli/chunks/{skills-N4D3POIS.js → skills-TDFQAZ6A.js} +2 -2
  127. package/dist-cli/chunks/{state-ES6TFQZF.js → state-P2Z25OLQ.js} +2 -2
  128. package/dist-cli/chunks/{storage-52DR5V2C.js → storage-DWEOFXEB.js} +2 -2
  129. package/dist-cli/chunks/store-TKBLMYCV.js +4 -0
  130. package/dist-cli/chunks/telemetry-E3CBKJMD.js +4 -0
  131. package/dist-cli/chunks/{timeline-NRVNB5JR.js → timeline-55XNA4GX.js} +2 -2
  132. package/dist-cli/chunks/{upgrade-LNED5YQ2.js → upgrade-3XRCVDVC.js} +2 -2
  133. package/dist-cli/chunks/upload-QB77GSMQ.js +4 -0
  134. package/dist-cli/chunks/{version-VEF2DYGG.js → version-WTYGLXBJ.js} +2 -2
  135. package/dist-cli/chunks/{web-CGDE6S73.js → web-ZWRY3VHM.js} +2 -2
  136. package/dist-cli/chunks/welcome-7ATM5EGX.js +5 -0
  137. package/dist-cli/chunks/{what-happened-FSIDWL32.js → what-happened-Q3IGIH4K.js} +2 -2
  138. package/dist-cli/chunks/{ws-bridge-QXFQLRE7.js → ws-bridge-KCPZSBBX.js} +2 -2
  139. package/dist-lib/agent-daemon-client.cjs +1 -1
  140. package/dist-lib/agent-events.cjs +1 -1
  141. package/dist-lib/agent-identity.cjs +1 -1
  142. package/dist-lib/agent-sessions.cjs +1 -1
  143. package/dist-lib/attached-projects.cjs +1 -1
  144. package/dist-lib/auth/shared-session.cjs +1 -1
  145. package/dist-lib/backend-origin.cjs +1 -1
  146. package/dist-lib/beta.cjs +1 -1
  147. package/dist-lib/beta.mjs +1 -1
  148. package/dist-lib/bridge-constants.cjs +1 -1
  149. package/dist-lib/bridge-contract-input.cjs +1 -1
  150. package/dist-lib/bridge-contract-input.mjs +1 -1
  151. package/dist-lib/bridge-contract.cjs +1 -1
  152. package/dist-lib/bridge-contract.mjs +1 -1
  153. package/dist-lib/cli-constants.cjs +1 -1
  154. package/dist-lib/config.cjs +1 -1
  155. package/dist-lib/detox/index.cjs +1 -1
  156. package/dist-lib/dev-bundle-resolution.cjs +69 -34
  157. package/dist-lib/home-paths.cjs +1 -1
  158. package/dist-lib/host/bridge-host.cjs +430 -106
  159. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  160. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  161. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  162. package/dist-lib/host/replacement-module-handler.cjs +1 -1
  163. package/dist-lib/host/websocket-proxy.cjs +1 -1
  164. package/dist-lib/index.cjs +1 -1
  165. package/dist-lib/jump-to-source-babel.cjs +1 -1
  166. package/dist-lib/menu.cjs +1 -1
  167. package/dist-lib/menu.mjs +1 -1
  168. package/dist-lib/metro-production-bundle.cjs +1 -1
  169. package/dist-lib/metro-production-bundle.mjs +1 -1
  170. package/dist-lib/metro.cjs +1 -1
  171. package/dist-lib/profiles.cjs +1 -1
  172. package/dist-lib/public-brand.cjs +1 -1
  173. package/dist-lib/react-native-host-modules.cjs +1 -1
  174. package/dist-lib/react-native-host-modules.mjs +1 -1
  175. package/dist-lib/render-mode.cjs +1 -1
  176. package/dist-lib/scripts/dev-server-scanner.cjs +149 -93
  177. package/dist-lib/sdk.cjs +1 -1
  178. package/dist-lib/sdk.mjs +1 -1
  179. package/dist-lib/skills.cjs +679 -307
  180. package/dist-lib/vite.cjs +1 -1
  181. package/package.json +1 -1
  182. package/scripts/dev-server-scanner.ts +275 -92
  183. package/src/dev-bundle-resolution.ts +123 -40
  184. package/src/disk-cleanup.ts +44 -0
  185. package/src/host/bridge-host.ts +11 -0
  186. package/src/host/plane-host.ts +203 -0
  187. package/dist-cli/chunks/chunk-GJRLPJQL.js +0 -6
  188. package/dist-cli/chunks/chunk-NTC46LSA.js +0 -4
  189. package/dist-cli/chunks/chunk-OTFTMGAL.js +0 -7
  190. package/dist-cli/chunks/cli-version-MLS55UTY.js +0 -4
  191. package/dist-cli/chunks/control-XD5F6K5T.js +0 -4
  192. package/dist-cli/chunks/daemon-UN5SE2JO.js +0 -4
  193. package/dist-cli/chunks/disk-cleanup-BDRHX5ZG.js +0 -4
  194. package/dist-cli/chunks/drivers-3NJ5RL6X.js +0 -4
  195. package/dist-cli/chunks/flow-FHZZD3P6.js +0 -4
  196. package/dist-cli/chunks/help-BYYDUSXF.js +0 -4
  197. package/dist-cli/chunks/inspect-RQCTWDCC.js +0 -4
  198. package/dist-cli/chunks/install-desktop-GOWXCQUE.js +0 -4
  199. package/dist-cli/chunks/privacy-MIFE72CG.js +0 -4
  200. package/dist-cli/chunks/reset-RZ4O5AFJ.js +0 -4
  201. package/dist-cli/chunks/resolve-target-7R5BNYMM.js +0 -4
  202. package/dist-cli/chunks/runtime-MZHLSRS6.js +0 -4
  203. package/dist-cli/chunks/setup-YFP3KEQZ.js +0 -4
  204. package/dist-cli/chunks/store-HOL45WCI.js +0 -4
  205. package/dist-cli/chunks/telemetry-BV6OTYPF.js +0 -4
  206. package/dist-cli/chunks/upload-RXKHFIBU.js +0 -4
  207. package/dist-cli/chunks/welcome-FT2AMUWW.js +0 -5
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.370 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.372 | (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;
@@ -24681,21 +25053,21 @@ function assertExplicitBridgeLock(preferredPort, explicitPort, lock) {
24681
25053
  }
24682
25054
  async function ensureRuntimeForOpen() {
24683
25055
  const active = readActiveRuntime();
24684
- if (active && import_node_fs13.default.existsSync(runtimeDir(active))) return;
25056
+ if (active && import_node_fs15.default.existsSync(runtimeDir(active))) return;
24685
25057
  console.error(" installing rnx engine runtime...");
24686
25058
  const { runRuntime: runRuntime2 } = await Promise.resolve().then(() => (init_runtime(), runtime_exports));
24687
25059
  await runRuntime2(["install"], {});
24688
25060
  }
24689
25061
  function spawnSootsim(args) {
24690
25062
  const entry = process.argv[1];
24691
- if (entry && import_node_fs13.default.existsSync(entry)) {
24692
- 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], {
24693
25065
  detached: true,
24694
25066
  stdio: "ignore",
24695
25067
  env: process.env
24696
25068
  });
24697
25069
  }
24698
- return (0, import_node_child_process6.spawn)(rnxPublicBrand.commandName, args, {
25070
+ return (0, import_node_child_process7.spawn)(rnxPublicBrand.commandName, args, {
24699
25071
  detached: true,
24700
25072
  stdio: "ignore",
24701
25073
  env: process.env
@@ -24776,7 +25148,7 @@ async function resolveConnectionInputForCli(target) {
24776
25148
  const registeredPort = /^\d+$/.test(normalized) ? Number(normalized) : localBasePort(normalized);
24777
25149
  if (registeredPort && registeredPort > 0) {
24778
25150
  const discovered = await probePort(registeredPort);
24779
- if (discovered) {
25151
+ if (discovered && !discovered.bundleUrlProvisional) {
24780
25152
  return {
24781
25153
  bundleUrl: discovered.bundleUrl,
24782
25154
  port: discovered.port,
@@ -24835,10 +25207,10 @@ async function buildResolvedBundleOpenUrl(target, baseUrl, opts = {}) {
24835
25207
  }
24836
25208
  function normalizeReplacementPath(filePath) {
24837
25209
  if (filePath.startsWith("~/")) {
24838
- 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));
24839
25211
  }
24840
- if (import_node_path16.default.isAbsolute(filePath)) return filePath;
24841
- 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);
24842
25214
  }
24843
25215
  function parseOpenRuntimeConfig(args) {
24844
25216
  const modules = {};
@@ -24857,7 +25229,7 @@ function parseOpenRuntimeConfig(args) {
24857
25229
  }
24858
25230
  const moduleName = raw.slice(0, eqIndex).trim();
24859
25231
  const filePath = normalizeReplacementPath(raw.slice(eqIndex + 1).trim());
24860
- if (!import_node_fs13.default.existsSync(filePath)) {
25232
+ if (!import_node_fs15.default.existsSync(filePath)) {
24861
25233
  console.error(` rnx open: replacement file not found: ${filePath}`);
24862
25234
  rnxExit(1);
24863
25235
  }
@@ -24888,7 +25260,7 @@ async function resolveOpenAppProject(resolvedBundle) {
24888
25260
  return advertisedRoot ? resolveAppProject(advertisedRoot) : null;
24889
25261
  }
24890
25262
  async function ensureConfiguredRuntime(version) {
24891
- 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;
24892
25264
  console.error(` installing configured engine runtime ${version}...`);
24893
25265
  const result = await sootsimRuntime.install({
24894
25266
  version,
@@ -25248,7 +25620,7 @@ function playwrightHostPidForSim(sim) {
25248
25620
  function signalDriverLaunchConnected(result) {
25249
25621
  if (!result.connectAckFile) return false;
25250
25622
  try {
25251
- import_node_fs13.default.writeFileSync(
25623
+ import_node_fs15.default.writeFileSync(
25252
25624
  result.connectAckFile,
25253
25625
  `${JSON.stringify({ connectedAt: Date.now(), pid: result.pid ?? null })}
25254
25626
  `,
@@ -25262,19 +25634,19 @@ function signalDriverLaunchConnected(result) {
25262
25634
  function printDriverDiagnosticLogTail(result) {
25263
25635
  if (!result.diagnosticLogPath) return;
25264
25636
  try {
25265
- const detail = import_node_fs13.default.readFileSync(result.diagnosticLogPath, "utf8").trim();
25637
+ const detail = import_node_fs15.default.readFileSync(result.diagnosticLogPath, "utf8").trim();
25266
25638
  if (!detail) {
25267
- console.error(` ${import_node_path16.default.basename(result.diagnosticLogPath)} was empty`);
25639
+ console.error(` ${import_node_path18.default.basename(result.diagnosticLogPath)} was empty`);
25268
25640
  return;
25269
25641
  }
25270
25642
  const shown = detail.length > 4e3 ? `\u2026
25271
25643
  ${detail.slice(-4e3)}` : detail;
25272
- console.error(` ${import_node_path16.default.basename(result.diagnosticLogPath)}:`);
25644
+ console.error(` ${import_node_path18.default.basename(result.diagnosticLogPath)}:`);
25273
25645
  console.error(shown);
25274
25646
  } catch (err) {
25275
25647
  const message = err instanceof Error ? err.message : String(err);
25276
25648
  console.error(
25277
- ` failed to read ${import_node_path16.default.basename(result.diagnosticLogPath)}: ${message}`
25649
+ ` failed to read ${import_node_path18.default.basename(result.diagnosticLogPath)}: ${message}`
25278
25650
  );
25279
25651
  }
25280
25652
  }
@@ -25295,11 +25667,11 @@ async function waitForProcessExit(pid, timeoutMs = 2500) {
25295
25667
  return !isProcessAlive2(pid);
25296
25668
  }
25297
25669
  function isPlaywrightHostProcess(pid) {
25298
- 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)], {
25299
25671
  encoding: "utf8",
25300
25672
  stdio: ["ignore", "pipe", "pipe"]
25301
25673
  });
25302
- 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)], {
25303
25675
  encoding: "utf8",
25304
25676
  stdio: ["ignore", "pipe", "pipe"]
25305
25677
  });
@@ -26071,14 +26443,14 @@ async function runCloseCommand(args, opts = {}) {
26071
26443
  bridge.close();
26072
26444
  }
26073
26445
  }
26074
- 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;
26075
26447
  var init_control = __esm({
26076
26448
  "cli/commands/control.ts"() {
26077
26449
  "use strict";
26078
- import_node_child_process6 = require("node:child_process");
26079
- 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);
26080
26452
  import_node_os3 = __toESM(require("node:os"), 1);
26081
- import_node_path16 = __toESM(require("node:path"), 1);
26453
+ import_node_path18 = __toESM(require("node:path"), 1);
26082
26454
  import_settings = require("sootsim-engine/settings");
26083
26455
  import_ws5 = require("ws");
26084
26456
  init_dev_server_scanner();
@@ -26942,14 +27314,14 @@ process.stdin.on('end', async () => {
26942
27314
  function getBaseDir() {
26943
27315
  const explicit = process.env[SESSION_FILE_ENV];
26944
27316
  if (explicit?.trim()) {
26945
- 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));
26946
27318
  }
26947
27319
  return electronUserDataDir();
26948
27320
  }
26949
27321
  function getSharedDesktopAuthFilePath() {
26950
27322
  const explicit = process.env[SESSION_FILE_ENV];
26951
- if (explicit?.trim()) return (0, import_node_path17.resolve)(explicit);
26952
- 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");
26953
27325
  }
26954
27326
  function normalizeUser(input) {
26955
27327
  if (!input || typeof input !== "object") return null;
@@ -26996,26 +27368,26 @@ function normalizeSession(input) {
26996
27368
  }
26997
27369
  function readSharedDesktopAuthSession() {
26998
27370
  const filepath = getSharedDesktopAuthFilePath();
26999
- if (!(0, import_node_fs14.existsSync)(filepath)) return null;
27371
+ if (!(0, import_node_fs16.existsSync)(filepath)) return null;
27000
27372
  try {
27001
- const parsed = JSON.parse((0, import_node_fs14.readFileSync)(filepath, "utf8"));
27373
+ const parsed = JSON.parse((0, import_node_fs16.readFileSync)(filepath, "utf8"));
27002
27374
  const normalized = normalizeSession(parsed);
27003
27375
  if (!normalized) {
27004
- (0, import_node_fs14.rmSync)(filepath, { force: true });
27376
+ (0, import_node_fs16.rmSync)(filepath, { force: true });
27005
27377
  return null;
27006
27378
  }
27007
27379
  return normalized;
27008
27380
  } catch {
27009
- (0, import_node_fs14.rmSync)(filepath, { force: true });
27381
+ (0, import_node_fs16.rmSync)(filepath, { force: true });
27010
27382
  return null;
27011
27383
  }
27012
27384
  }
27013
- 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;
27014
27386
  var init_shared_session = __esm({
27015
27387
  "src/auth/shared-session.ts"() {
27016
27388
  "use strict";
27017
- import_node_fs14 = require("node:fs");
27018
- import_node_path17 = require("node:path");
27389
+ import_node_fs16 = require("node:fs");
27390
+ import_node_path19 = require("node:path");
27019
27391
  init_home_paths();
27020
27392
  SESSION_VERSION = 1;
27021
27393
  SESSION_FILE_ENV = "SOOTSIM_SHARED_AUTH_FILE";
@@ -27257,16 +27629,16 @@ function resolveFlowScreenshotPath(screenshotDir, name2) {
27257
27629
  }
27258
27630
  function resolveProjectRelativeFlowBase(flowDir) {
27259
27631
  if (!flowDir) return process.cwd();
27260
- return path16.basename(flowDir) === ".maestro" ? path16.dirname(flowDir) : flowDir;
27632
+ return path18.basename(flowDir) === ".maestro" ? path18.dirname(flowDir) : flowDir;
27261
27633
  }
27262
27634
  function resolveFlowScreenshotPathWithMode(screenshotDir, name2, opts) {
27263
27635
  const normalizedName = name2.endsWith(".png") ? name2 : `${name2}.png`;
27264
- if (path16.isAbsolute(normalizedName)) return normalizedName;
27636
+ if (path18.isAbsolute(normalizedName)) return normalizedName;
27265
27637
  if (opts.mode === "flow" && /[\\/]/.test(normalizedName)) {
27266
27638
  const baseDir = normalizedName.startsWith("./") || normalizedName.startsWith("../") ? opts.flowDir ?? process.cwd() : resolveProjectRelativeFlowBase(opts.flowDir);
27267
- return path16.resolve(baseDir, normalizedName);
27639
+ return path18.resolve(baseDir, normalizedName);
27268
27640
  }
27269
- return path16.join(screenshotDir, normalizedName);
27641
+ return path18.join(screenshotDir, normalizedName);
27270
27642
  }
27271
27643
  function normalizeFlowScreenshotSpec(step) {
27272
27644
  if (typeof step === "string") {
@@ -27323,12 +27695,12 @@ function parseFlowSteps(content) {
27323
27695
  const parsed = import_yaml.default.parse(candidate);
27324
27696
  return Array.isArray(parsed) ? parsed : [];
27325
27697
  }
27326
- 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;
27327
27699
  var init_bridge_flow_runner = __esm({
27328
27700
  "cli/bridge-flow-runner.ts"() {
27329
27701
  "use strict";
27330
- fs16 = __toESM(require("fs"), 1);
27331
- path16 = __toESM(require("path"), 1);
27702
+ fs18 = __toESM(require("fs"), 1);
27703
+ path18 = __toESM(require("path"), 1);
27332
27704
  import_settings3 = require("sootsim-engine/settings");
27333
27705
  import_yaml = __toESM(require("yaml"), 1);
27334
27706
  import_proof_frame = __toESM(require_proof_frame(), 1);
@@ -27535,14 +27907,14 @@ var init_bridge_flow_runner = __esm({
27535
27907
  if (chunks.length === 0) {
27536
27908
  throw new Error("recording requested but no video buffer was produced");
27537
27909
  }
27538
- fs16.mkdirSync(this.opts.recordingOutputDir, { recursive: true });
27910
+ fs18.mkdirSync(this.opts.recordingOutputDir, { recursive: true });
27539
27911
  const videoMime = stopped.mime || this.opts.recordingFormat || "video/webm";
27540
27912
  const extension = videoMime.includes("mp4") ? "mp4" : "webm";
27541
- const outputPath = path16.join(
27913
+ const outputPath = path18.join(
27542
27914
  this.opts.recordingOutputDir,
27543
27915
  `sootsim-${Date.now()}.${extension}`
27544
27916
  );
27545
- fs16.writeFileSync(outputPath, Buffer.concat(chunks));
27917
+ fs18.writeFileSync(outputPath, Buffer.concat(chunks));
27546
27918
  return outputPath;
27547
27919
  }
27548
27920
  async waitForRecordingTail(opts) {
@@ -28038,7 +28410,7 @@ var init_bridge_flow_runner = __esm({
28038
28410
  flowDir: this.opts.flowDir
28039
28411
  }
28040
28412
  );
28041
- fs16.mkdirSync(path16.dirname(outputPath), { recursive: true });
28413
+ fs18.mkdirSync(path18.dirname(outputPath), { recursive: true });
28042
28414
  const layers = spec.layers ?? this.opts.screenshotLayers;
28043
28415
  const screenshotRequest = {
28044
28416
  type: "screenshot",
@@ -28077,11 +28449,11 @@ var init_bridge_flow_runner = __esm({
28077
28449
  throw new Error("could not read current device model for framed screenshot");
28078
28450
  }
28079
28451
  const framed = await composeFramedScreenshot(rawBuffer, model);
28080
- fs16.writeFileSync(outputPath, framed);
28452
+ fs18.writeFileSync(outputPath, framed);
28081
28453
  console.log(`[flow] screenshot: ${outputPath} (frame: ${model})`);
28082
28454
  return outputPath;
28083
28455
  }
28084
- fs16.writeFileSync(outputPath, rawBuffer);
28456
+ fs18.writeFileSync(outputPath, rawBuffer);
28085
28457
  console.log(`[flow] screenshot: ${outputPath}`);
28086
28458
  return outputPath;
28087
28459
  }
@@ -28103,10 +28475,10 @@ var init_bridge_flow_runner = __esm({
28103
28475
  // + a11y tree + console errors/warnings + failed network requests +
28104
28476
  // shell state, all under one directory so post-mortem is one grep away.
28105
28477
  async captureFailureBundle(outputDir, context) {
28106
- fs16.mkdirSync(outputDir, { recursive: true });
28478
+ fs18.mkdirSync(outputDir, { recursive: true });
28107
28479
  const write = (name2, content) => {
28108
28480
  try {
28109
- fs16.writeFileSync(path16.join(outputDir, name2), content);
28481
+ fs18.writeFileSync(path18.join(outputDir, name2), content);
28110
28482
  } catch {
28111
28483
  }
28112
28484
  };
@@ -28644,8 +29016,8 @@ var init_bridge_flow_runner = __esm({
28644
29016
  runScript(target) {
28645
29017
  const spec = typeof target === "string" ? { file: target } : target;
28646
29018
  if (!spec.file) throw new Error("runScript requires a file");
28647
- const scriptPath = path16.resolve(this.opts.flowDir, spec.file);
28648
- const source = fs16.readFileSync(scriptPath, "utf8");
29019
+ const scriptPath = path18.resolve(this.opts.flowDir, spec.file);
29020
+ const source = fs18.readFileSync(scriptPath, "utf8");
28649
29021
  this.js.evaluate(source, { env: spec.env });
28650
29022
  console.log(`[flow] runScript: ${spec.file} done`);
28651
29023
  }
@@ -28942,10 +29314,10 @@ var init_bridge_flow_runner = __esm({
28942
29314
  for (const [k, v] of Object.entries(env)) this.js.putEnv(k, v);
28943
29315
  }
28944
29316
  if (file) {
28945
- const flowPath = path16.resolve(this.opts.flowDir, file);
28946
- const flowContent = fs16.readFileSync(flowPath, "utf8");
29317
+ const flowPath = path18.resolve(this.opts.flowDir, file);
29318
+ const flowContent = fs18.readFileSync(flowPath, "utf8");
28947
29319
  const previousDir = this.opts.flowDir;
28948
- this.opts.flowDir = path16.dirname(flowPath);
29320
+ this.opts.flowDir = path18.dirname(flowPath);
28949
29321
  try {
28950
29322
  await this.runFlow(parseFlowSteps(flowContent));
28951
29323
  } finally {
@@ -29100,8 +29472,8 @@ __export(registry_exports, {
29100
29472
  skillRegistry: () => skillRegistry
29101
29473
  });
29102
29474
  module.exports = __toCommonJS(registry_exports);
29103
- var fs17 = __toESM(require("fs"), 1);
29104
- var path17 = __toESM(require("path"), 1);
29475
+ var fs19 = __toESM(require("fs"), 1);
29476
+ var path19 = __toESM(require("path"), 1);
29105
29477
 
29106
29478
  // src/skills/builtin/a11y-review.ts
29107
29479
  init_reap_browser();
@@ -29120,8 +29492,8 @@ var skill = {
29120
29492
  },
29121
29493
  async execute(params, context) {
29122
29494
  const { chromium } = await import("playwright");
29123
- const path18 = await import("path");
29124
- const fs18 = await import("fs");
29495
+ const path20 = await import("path");
29496
+ const fs20 = await import("fs");
29125
29497
  const browser = await launchReapedChrome((options) => chromium.launch(options), {
29126
29498
  headless: true
29127
29499
  });
@@ -29176,9 +29548,9 @@ var skill = {
29176
29548
  warnings: issues.filter((i) => i.severity === "warning").length,
29177
29549
  tree: typeof tree === "string" ? tree : JSON.stringify(tree)
29178
29550
  };
29179
- const reportPath = path18.join(context.outputDir, "a11y-report.json");
29180
- fs18.mkdirSync(path18.dirname(reportPath), { recursive: true });
29181
- 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));
29182
29554
  return {
29183
29555
  success: issues.filter((i) => i.severity === "error").length === 0,
29184
29556
  message: `${report.errors} errors, ${report.warnings} warnings across ${report.nodeCount} nodes`,
@@ -29211,14 +29583,14 @@ var skill2 = {
29211
29583
  }
29212
29584
  },
29213
29585
  async execute(params, context) {
29214
- const fs18 = await import("node:fs");
29215
- const path18 = await import("node:path");
29586
+ const fs20 = await import("node:fs");
29587
+ const path20 = await import("node:path");
29216
29588
  const { COMPAT_RESULT_CAVEAT: COMPAT_RESULT_CAVEAT2, formatPackageStatus: formatPackageStatus2, formatScanCounts: formatScanCounts2, scanDeps: scanDeps2 } = await Promise.resolve().then(() => (init_web(), web_exports));
29217
- const packagePath = path18.join(context.projectDir, "package.json");
29218
- if (!fs18.existsSync(packagePath)) {
29589
+ const packagePath = path20.join(context.projectDir, "package.json");
29590
+ if (!fs20.existsSync(packagePath)) {
29219
29591
  return { success: false, message: "No package.json found" };
29220
29592
  }
29221
- const parsed = JSON.parse(fs18.readFileSync(packagePath, "utf8"));
29593
+ const parsed = JSON.parse(fs20.readFileSync(packagePath, "utf8"));
29222
29594
  if (!parsed || typeof parsed !== "object") {
29223
29595
  return { success: false, message: "package.json is not an object" };
29224
29596
  }
@@ -29230,9 +29602,9 @@ var skill2 = {
29230
29602
  }) : scanDeps2(parsed);
29231
29603
  const packageResult = params.packageName ? scan.packages[0] : void 0;
29232
29604
  const message = packageResult ? `${params.packageName}: ${formatPackageStatus2(packageResult)}. ${COMPAT_RESULT_CAVEAT2}` : `${formatScanCounts2(scan)}. ${COMPAT_RESULT_CAVEAT2}`;
29233
- const reportPath = path18.join(context.outputDir, "compat-report.json");
29234
- fs18.mkdirSync(path18.dirname(reportPath), { recursive: true });
29235
- 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)}
29236
29608
  `);
29237
29609
  return {
29238
29610
  success: true,
@@ -29276,8 +29648,8 @@ var skill3 = {
29276
29648
  record: { type: "boolean", description: "Record video of the flow" }
29277
29649
  },
29278
29650
  async execute(params, context) {
29279
- const fs18 = await import("fs");
29280
- const path18 = await import("path");
29651
+ const fs20 = await import("fs");
29652
+ const path20 = await import("path");
29281
29653
  const yaml2 = await import("yaml");
29282
29654
  const { DEFAULT_SOOTSIM_BRIDGE_PORT: DEFAULT_SOOTSIM_BRIDGE_PORT2 } = await Promise.resolve().then(() => (init_bridge_constants(), bridge_constants_exports));
29283
29655
  const { SootSimBridgeHost: SootSimBridgeHost2 } = await Promise.resolve().then(() => (init_bridge_host(), bridge_host_exports));
@@ -29287,7 +29659,7 @@ var skill3 = {
29287
29659
  const host = new SootSimBridgeHost2({ port: DEFAULT_SOOTSIM_BRIDGE_PORT2 });
29288
29660
  host.start({ silent: true });
29289
29661
  try {
29290
- const flowContent = fs18.readFileSync(params.flowPath, "utf8");
29662
+ const flowContent = fs20.readFileSync(params.flowPath, "utf8");
29291
29663
  const fmMatch = flowContent.match(/^---\n([\s\S]*?)\n---\n?/);
29292
29664
  const stepsBody = fmMatch ? flowContent.slice(fmMatch[0].length) : flowContent;
29293
29665
  const steps = yaml2.parse(stepsBody);
@@ -29314,7 +29686,7 @@ var skill3 = {
29314
29686
  });
29315
29687
  const driver = new SootSimBridgeFlowRunner2(bridge, {
29316
29688
  screenshotDir: context.outputDir,
29317
- flowDir: path18.dirname(path18.resolve(params.flowPath)),
29689
+ flowDir: path20.dirname(path20.resolve(params.flowPath)),
29318
29690
  simId,
29319
29691
  recordingOutputDir: params.record ? context.outputDir : void 0
29320
29692
  });
@@ -29336,7 +29708,7 @@ var skill3 = {
29336
29708
  simId,
29337
29709
  videoPath: videoPath || void 0
29338
29710
  },
29339
- 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
29340
29712
  };
29341
29713
  } finally {
29342
29714
  try {
@@ -29383,8 +29755,8 @@ var skill4 = {
29383
29755
  },
29384
29756
  async execute(params, context) {
29385
29757
  const { chromium } = await import("playwright");
29386
- const path18 = await import("path");
29387
- const fs18 = await import("fs");
29758
+ const path20 = await import("path");
29759
+ const fs20 = await import("fs");
29388
29760
  const browser = await launchReapedChrome((options) => chromium.launch(options), {
29389
29761
  headless: true
29390
29762
  });
@@ -29410,9 +29782,9 @@ var skill4 = {
29410
29782
  ...profile,
29411
29783
  fps: syncCount > 0 ? (syncCount / (duration / 1e3)).toFixed(1) : "0"
29412
29784
  };
29413
- const reportPath = path18.join(context.outputDir, "perf-report.json");
29414
- fs18.mkdirSync(path18.dirname(reportPath), { recursive: true });
29415
- 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));
29416
29788
  return {
29417
29789
  success: true,
29418
29790
  message: `${report.fps} fps, ${profile.avgSyncMs?.toFixed(1)}ms avg sync, ${nodeCount} nodes`,
@@ -29445,8 +29817,8 @@ var skill5 = {
29445
29817
  },
29446
29818
  async execute(params, context) {
29447
29819
  const { chromium } = await import("playwright");
29448
- const path18 = await import("path");
29449
- const fs18 = await import("fs");
29820
+ const path20 = await import("path");
29821
+ const fs20 = await import("fs");
29450
29822
  const browser = await launchReapedChrome((options) => chromium.launch(options), {
29451
29823
  headless: true
29452
29824
  });
@@ -29454,8 +29826,8 @@ var skill5 = {
29454
29826
  try {
29455
29827
  await page.goto(context.url, { waitUntil: "networkidle" });
29456
29828
  await page.waitForTimeout(2e3);
29457
- const outputPath = path18.join(context.outputDir, `${params.name}.png`);
29458
- 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 });
29459
29831
  await page.screenshot({ path: outputPath });
29460
29832
  return {
29461
29833
  success: true,
@@ -29496,8 +29868,8 @@ var skill6 = {
29496
29868
  },
29497
29869
  async execute(params, context) {
29498
29870
  const { chromium } = await import("playwright");
29499
- const path18 = await import("path");
29500
- const fs18 = await import("fs");
29871
+ const path20 = await import("path");
29872
+ const fs20 = await import("fs");
29501
29873
  const browser = await launchReapedChrome((options) => chromium.launch(options), {
29502
29874
  headless: true
29503
29875
  });
@@ -29505,14 +29877,14 @@ var skill6 = {
29505
29877
  try {
29506
29878
  await page.goto(context.url, { waitUntil: "networkidle" });
29507
29879
  await page.waitForTimeout(2e3);
29508
- const currentPath = path18.join(context.outputDir, `${params.name}-current.png`);
29509
- 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 });
29510
29882
  await page.screenshot({ path: currentPath });
29511
29883
  try {
29512
29884
  const { PNG } = await import("pngjs");
29513
29885
  const pixelmatch = (await import("pixelmatch")).default;
29514
- const baseline = PNG.sync.read(fs18.readFileSync(params.baseline));
29515
- 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));
29516
29888
  const { width, height } = baseline;
29517
29889
  const diff = new PNG({ width, height });
29518
29890
  const numDiffPixels = pixelmatch(
@@ -29523,8 +29895,8 @@ var skill6 = {
29523
29895
  height,
29524
29896
  { threshold: params.threshold || 0.1 }
29525
29897
  );
29526
- const diffPath = path18.join(context.outputDir, `${params.name}-diff.png`);
29527
- 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));
29528
29900
  const totalPixels = width * height;
29529
29901
  const diffPercent = (numDiffPixels / totalPixels * 100).toFixed(2);
29530
29902
  return {
@@ -29588,10 +29960,10 @@ var skillRegistry = {
29588
29960
  },
29589
29961
  // load skills from a project manifest (sootsim-skills.json)
29590
29962
  async loadFromManifest(projectDir) {
29591
- const manifestPath = path17.join(projectDir, "sootsim-skills.json");
29592
- if (!fs17.existsSync(manifestPath)) return;
29963
+ const manifestPath = path19.join(projectDir, "sootsim-skills.json");
29964
+ if (!fs19.existsSync(manifestPath)) return;
29593
29965
  try {
29594
- const manifest = JSON.parse(fs17.readFileSync(manifestPath, "utf8"));
29966
+ const manifest = JSON.parse(fs19.readFileSync(manifestPath, "utf8"));
29595
29967
  if (Array.isArray(manifest.skills)) {
29596
29968
  for (const entry of manifest.skills) {
29597
29969
  if (typeof entry === "string") {
@@ -29602,7 +29974,7 @@ var skillRegistry = {
29602
29974
  }
29603
29975
  } else if (entry.file) {
29604
29976
  try {
29605
- const mod = await import(path17.resolve(projectDir, entry.file));
29977
+ const mod = await import(path19.resolve(projectDir, entry.file));
29606
29978
  if (mod.skill) this.register(mod.skill);
29607
29979
  } catch {
29608
29980
  }
@@ -29615,10 +29987,10 @@ var skillRegistry = {
29615
29987
  // auto-discover skills based on project dependencies
29616
29988
  suggestSkills(projectDir) {
29617
29989
  const suggestions = [];
29618
- const pkgPath = path17.join(projectDir, "package.json");
29619
- if (!fs17.existsSync(pkgPath)) return suggestions;
29990
+ const pkgPath = path19.join(projectDir, "package.json");
29991
+ if (!fs19.existsSync(pkgPath)) return suggestions;
29620
29992
  try {
29621
- const pkg = JSON.parse(fs17.readFileSync(pkgPath, "utf8"));
29993
+ const pkg = JSON.parse(fs19.readFileSync(pkgPath, "utf8"));
29622
29994
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
29623
29995
  if (deps["detox"] || deps["maestro"]) {
29624
29996
  suggestions.push("maestro-test");