rnxsim 0.1.313 → 0.1.314

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (297) hide show
  1. package/LICENSE +11 -10
  2. package/README.md +5 -0
  3. package/cli/app-config.ts +65 -0
  4. package/cli/app-fonts.ts +408 -0
  5. package/cli/app-project.ts +231 -0
  6. package/cli/app-splash.ts +185 -0
  7. package/cli/app-state-reset.ts +24 -0
  8. package/cli/auth.ts +155 -0
  9. package/cli/bin.ts +594 -0
  10. package/cli/bridge-diagnostics.ts +226 -0
  11. package/cli/bridge-flow-runner.ts +2830 -0
  12. package/cli/browser-evals.ts +96 -0
  13. package/cli/commands/agent-wrapper.ts +986 -0
  14. package/cli/commands/agent.ts +423 -0
  15. package/cli/commands/app-fonts.ts +98 -0
  16. package/cli/commands/assert.ts +541 -0
  17. package/cli/commands/auth.ts +59 -0
  18. package/cli/commands/camera.ts +266 -0
  19. package/cli/commands/cleanup.ts +169 -0
  20. package/cli/commands/compat.ts +87 -0
  21. package/cli/commands/config.ts +32 -0
  22. package/cli/commands/control.ts +2142 -0
  23. package/cli/commands/cpu-profile.ts +269 -0
  24. package/cli/commands/daemon-mac-app.ts +169 -0
  25. package/cli/commands/daemon.ts +874 -0
  26. package/cli/commands/debug.ts +719 -0
  27. package/cli/commands/desktop.ts +39 -0
  28. package/cli/commands/detect.ts +197 -0
  29. package/cli/commands/detox.ts +385 -0
  30. package/cli/commands/device.ts +133 -0
  31. package/cli/commands/diagnose.ts +589 -0
  32. package/cli/commands/electron.ts +95 -0
  33. package/cli/commands/film.ts +379 -0
  34. package/cli/commands/flow.ts +1124 -0
  35. package/cli/commands/inspect/actions.ts +622 -0
  36. package/cli/commands/inspect/core.ts +2405 -0
  37. package/cli/commands/inspect/count.ts +17 -0
  38. package/cli/commands/inspect/describe.ts +192 -0
  39. package/cli/commands/inspect/env.ts +23 -0
  40. package/cli/commands/inspect/find.ts +171 -0
  41. package/cli/commands/inspect/get-layout.ts +39 -0
  42. package/cli/commands/inspect/keyboard.ts +52 -0
  43. package/cli/commands/inspect/list.ts +58 -0
  44. package/cli/commands/inspect/memory.ts +215 -0
  45. package/cli/commands/inspect/redaction.ts +39 -0
  46. package/cli/commands/inspect/resolve-target.ts +82 -0
  47. package/cli/commands/inspect/screens.ts +78 -0
  48. package/cli/commands/inspect/settle.ts +22 -0
  49. package/cli/commands/inspect/settling.ts +158 -0
  50. package/cli/commands/inspect/shared.ts +353 -0
  51. package/cli/commands/inspect/sleep.ts +14 -0
  52. package/cli/commands/inspect/tree.ts +32 -0
  53. package/cli/commands/inspect/url.ts +17 -0
  54. package/cli/commands/inspect/wait-event.ts +210 -0
  55. package/cli/commands/inspect/wait-idle.ts +24 -0
  56. package/cli/commands/inspect/wait-ready.ts +74 -0
  57. package/cli/commands/inspect/wait-selector.ts +54 -0
  58. package/cli/commands/inspect/wait.ts +31 -0
  59. package/cli/commands/inspect.ts +4519 -0
  60. package/cli/commands/install-desktop.ts +351 -0
  61. package/cli/commands/login.ts +331 -0
  62. package/cli/commands/logout.ts +31 -0
  63. package/cli/commands/maestro-generate.ts +361 -0
  64. package/cli/commands/maestro.ts +453 -0
  65. package/cli/commands/mode.ts +57 -0
  66. package/cli/commands/no-bridge-hint.ts +80 -0
  67. package/cli/commands/perf.ts +66 -0
  68. package/cli/commands/permissions.ts +203 -0
  69. package/cli/commands/profile.ts +108 -0
  70. package/cli/commands/react.ts +353 -0
  71. package/cli/commands/record.ts +1434 -0
  72. package/cli/commands/report-issue.ts +305 -0
  73. package/cli/commands/reset.ts +85 -0
  74. package/cli/commands/runtime.ts +351 -0
  75. package/cli/commands/screenshot-command.ts +106 -0
  76. package/cli/commands/screenshot-layers.ts +143 -0
  77. package/cli/commands/screenshot-mode.ts +37 -0
  78. package/cli/commands/screenshot.ts +488 -0
  79. package/cli/commands/screenshots-capture.ts +607 -0
  80. package/cli/commands/screenshots.ts +127 -0
  81. package/cli/commands/serve.ts +168 -0
  82. package/cli/commands/setup.ts +545 -0
  83. package/cli/commands/shell-boolean-mode.ts +81 -0
  84. package/cli/commands/skills.ts +467 -0
  85. package/cli/commands/slides.ts +361 -0
  86. package/cli/commands/state.ts +87 -0
  87. package/cli/commands/storage.ts +58 -0
  88. package/cli/commands/telemetry.ts +54 -0
  89. package/cli/commands/three-mode.ts +763 -0
  90. package/cli/commands/timeline.ts +122 -0
  91. package/cli/commands/upgrade.ts +208 -0
  92. package/cli/commands/upload.ts +1225 -0
  93. package/cli/commands/version.ts +54 -0
  94. package/cli/commands/what-happened.ts +327 -0
  95. package/cli/current-sim.ts +204 -0
  96. package/cli/desktop-companion.ts +300 -0
  97. package/cli/drivers/electron.ts +70 -0
  98. package/cli/drivers/index.ts +20 -0
  99. package/cli/drivers/playwright-provisioning.ts +180 -0
  100. package/cli/drivers/playwright.ts +698 -0
  101. package/cli/drivers/registry.ts +65 -0
  102. package/cli/drivers/types.ts +102 -0
  103. package/cli/flow-file.ts +142 -0
  104. package/cli/flow-live-status.ts +120 -0
  105. package/cli/flow-session.ts +187 -0
  106. package/cli/help.ts +80 -0
  107. package/cli/hidden-runtime-alias.ts +19 -0
  108. package/cli/hints.ts +216 -0
  109. package/cli/inspect-notice-state.ts +114 -0
  110. package/cli/maestro-js.ts +334 -0
  111. package/cli/open-url.ts +8 -0
  112. package/cli/parent-pid.ts +204 -0
  113. package/cli/parse-args.ts +211 -0
  114. package/cli/prompt.ts +51 -0
  115. package/cli/recording-access.ts +107 -0
  116. package/cli/registry.ts +1 -0
  117. package/cli/resolve-assets.ts +63 -0
  118. package/cli/run-registry.ts +226 -0
  119. package/cli/runtime-notes.ts +66 -0
  120. package/cli/runtime-summary.ts +25 -0
  121. package/cli/setup-repository.ts +187 -0
  122. package/cli/telemetry.ts +187 -0
  123. package/cli/ws-bridge.ts +798 -0
  124. package/dist-cli/bin.js +5 -5
  125. package/dist-cli/chunks/{agent-XZ2KTPCU.js → agent-7YBDCYMA.js} +2 -2
  126. package/dist-cli/chunks/{agent-wrapper-JJYYW2WH.js → agent-wrapper-2GHFBHCR.js} +2 -2
  127. package/dist-cli/chunks/{app-fonts-IXRNQG6B.js → app-fonts-RSNVPQSU.js} +2 -2
  128. package/dist-cli/chunks/{assert-54T5SK5F.js → assert-XCMX3XJX.js} +2 -2
  129. package/dist-cli/chunks/{auth-FI5UDI45.js → auth-B442HRAX.js} +2 -2
  130. package/dist-cli/chunks/{beta-JV6UKADW.js → beta-XJ55JK3M.js} +2 -2
  131. package/dist-cli/chunks/camera-UGYSSLIK.js +33 -0
  132. package/dist-cli/chunks/{chunk-3NV2NCNX.js → chunk-277AEQZX.js} +2 -2
  133. package/dist-cli/chunks/{chunk-2YR5BGA5.js → chunk-2JNSK774.js} +2 -2
  134. package/dist-cli/chunks/{chunk-RSZWCKNT.js → chunk-32WOTSTR.js} +3 -3
  135. package/dist-cli/chunks/{chunk-WEXDAC74.js → chunk-3S753SNQ.js} +2 -2
  136. package/dist-cli/chunks/{chunk-IJO63TDP.js → chunk-46ZOLOYA.js} +2 -2
  137. package/dist-cli/chunks/{chunk-WUSWBCWA.js → chunk-5L7ELDQL.js} +8 -9
  138. package/dist-cli/chunks/{chunk-WF3T4SVI.js → chunk-7OOPFSQS.js} +2 -2
  139. package/dist-cli/chunks/{chunk-TZFFR3SD.js → chunk-7SV3RPRW.js} +2 -2
  140. package/dist-cli/chunks/{chunk-WWZIXIRD.js → chunk-7ZC35MOU.js} +1 -1
  141. package/dist-cli/chunks/chunk-AMG5E6CC.js +9 -0
  142. package/dist-cli/chunks/{chunk-WINYQ44O.js → chunk-APWNH3A4.js} +1 -1
  143. package/dist-cli/chunks/chunk-B57XUKY3.js +4 -0
  144. package/dist-cli/chunks/{chunk-YDGQTMQL.js → chunk-C2NL26TD.js} +1 -1
  145. package/dist-cli/chunks/{chunk-NMF2ZMZQ.js → chunk-D2FNUWAB.js} +4 -4
  146. package/dist-cli/chunks/{chunk-YIFT42WN.js → chunk-E5T4XSJ3.js} +2 -2
  147. package/dist-cli/chunks/{chunk-46EUUFJ5.js → chunk-EAC34EQS.js} +1 -1
  148. package/dist-cli/chunks/{chunk-OVFJFXUD.js → chunk-EG32ML36.js} +2 -2
  149. package/dist-cli/chunks/{chunk-5YJCOWCH.js → chunk-FXUAC6D5.js} +1 -1
  150. package/dist-cli/chunks/chunk-GQSL4USA.js +6 -0
  151. package/dist-cli/chunks/{chunk-VFCMSYZK.js → chunk-HAXW27SS.js} +2 -2
  152. package/dist-cli/chunks/{chunk-2D2UPBBR.js → chunk-IZAHPAN6.js} +1 -1
  153. package/dist-cli/chunks/{chunk-7GN3LVWB.js → chunk-J62KM5TB.js} +2 -2
  154. package/dist-cli/chunks/{chunk-BTWORNNG.js → chunk-JEMCD5E4.js} +1 -1
  155. package/dist-cli/chunks/{chunk-DCEMHR2Y.js → chunk-JZS3Q37N.js} +2 -2
  156. package/dist-cli/chunks/{chunk-D4FFVGI5.js → chunk-KR4JON7D.js} +1 -1
  157. package/dist-cli/chunks/chunk-KWCYKQIQ.js +4 -0
  158. package/dist-cli/chunks/{chunk-VNQEB4L7.js → chunk-MCWPL644.js} +2 -2
  159. package/dist-cli/chunks/chunk-MJYK3N2I.js +4 -0
  160. package/dist-cli/chunks/{chunk-5TEF3ET3.js → chunk-O6TRIZNS.js} +2 -2
  161. package/dist-cli/chunks/{chunk-QKDWYITG.js → chunk-P7XL2E73.js} +3 -3
  162. package/dist-cli/chunks/{chunk-BBULZ7CG.js → chunk-PFQTUKQ4.js} +62 -87
  163. package/dist-cli/chunks/{chunk-GGRX24GF.js → chunk-PG5RZCTN.js} +2 -2
  164. package/dist-cli/chunks/{chunk-W6K4EFPH.js → chunk-QGRI2Z4M.js} +2 -2
  165. package/dist-cli/chunks/{chunk-ZMJD5GEC.js → chunk-QLXXE7GE.js} +1 -1
  166. package/dist-cli/chunks/{chunk-VZXWHRUZ.js → chunk-QOJJJWJE.js} +89 -133
  167. package/dist-cli/chunks/{chunk-OZSSI4WN.js → chunk-RWZY5427.js} +2 -2
  168. package/dist-cli/chunks/{chunk-UC6U3MML.js → chunk-RZKU2K3J.js} +2 -2
  169. package/dist-cli/chunks/{chunk-IJ5CAZZC.js → chunk-SBV4IK4H.js} +1 -1
  170. package/dist-cli/chunks/{chunk-DZS6WPUI.js → chunk-SGMVFFMK.js} +1 -1
  171. package/dist-cli/chunks/{chunk-OHAZNXLK.js → chunk-TAX4UT2N.js} +1 -1
  172. package/dist-cli/chunks/{chunk-5DHC6KHQ.js → chunk-TUOFAWXT.js} +1 -1
  173. package/dist-cli/chunks/{chunk-GASE6UBA.js → chunk-UHZLOHGP.js} +1 -1
  174. package/dist-cli/chunks/{chunk-F5ZRSS3C.js → chunk-VC7V76U3.js} +1 -1
  175. package/dist-cli/chunks/{chunk-RTN5C5RL.js → chunk-VQVMLW4U.js} +1 -1
  176. package/dist-cli/chunks/{chunk-WMIIKMGK.js → chunk-VUKKYPZN.js} +2 -2
  177. package/dist-cli/chunks/{chunk-5TPRP5QT.js → chunk-WGGRJDRE.js} +1 -1
  178. package/dist-cli/chunks/chunk-X6H76EKP.js +15 -0
  179. package/dist-cli/chunks/chunk-XLZ5FNRT.js +27 -0
  180. package/dist-cli/chunks/{chunk-HI5TFJWN.js → chunk-XULEACM4.js} +2 -2
  181. package/dist-cli/chunks/{chunk-MJRLLB4R.js → chunk-YFSDM7AX.js} +4 -4
  182. package/dist-cli/chunks/chunk-YWI3UEVX.js +5 -0
  183. package/dist-cli/chunks/{chunk-XEVZYVIW.js → chunk-ZBSJO4NB.js} +10 -9
  184. package/dist-cli/chunks/{cleanup-P27PA6JI.js → cleanup-EYLGCA6Z.js} +2 -2
  185. package/dist-cli/chunks/cli-version-LL2UGIHE.js +4 -0
  186. package/dist-cli/chunks/{compat-ZD65FED3.js → compat-TLJYHB4E.js} +2 -2
  187. package/dist-cli/chunks/{config-XMJRNM2A.js → config-4JWUOEXK.js} +2 -2
  188. package/dist-cli/chunks/{control-KMIQT3QP.js → control-HPAOYF4N.js} +2 -2
  189. package/dist-cli/chunks/daemon-OYLASXLE.js +4 -0
  190. package/dist-cli/chunks/{debug-PT4HOP7N.js → debug-4BKXF6KI.js} +5 -5
  191. package/dist-cli/chunks/{desktop-S3FG72AK.js → desktop-ZQ6ZD2S6.js} +3 -3
  192. package/dist-cli/chunks/{detox-B3D4IFCN.js → detox-WNPASSS3.js} +2 -2
  193. package/dist-cli/chunks/{device-XBNDSB2R.js → device-BXLXVG3V.js} +2 -2
  194. package/dist-cli/chunks/{diagnose-HMQXJE5N.js → diagnose-3QX7W5RW.js} +2 -2
  195. package/dist-cli/chunks/{disk-cleanup-BLCZ5BSZ.js → disk-cleanup-KSWKI7WB.js} +2 -2
  196. package/dist-cli/chunks/drivers-EPIEFF7P.js +4 -0
  197. package/dist-cli/chunks/{film-BJGTBYZB.js → film-GDMR33VO.js} +3 -3
  198. package/dist-cli/chunks/flow-NKMPBYCJ.js +4 -0
  199. package/dist-cli/chunks/help-OGCBHOKA.js +4 -0
  200. package/dist-cli/chunks/{hidden-runtime-alias-ANOYADHM.js → hidden-runtime-alias-S2GTDX3T.js} +2 -2
  201. package/dist-cli/chunks/home-paths-QRCDLTTV.js +4 -0
  202. package/dist-cli/chunks/inspect-FUYMOZPY.js +4 -0
  203. package/dist-cli/chunks/install-desktop-FR6YKW7Y.js +4 -0
  204. package/dist-cli/chunks/{login-FJ737MWG.js → login-IJAPUZHI.js} +4 -4
  205. package/dist-cli/chunks/{logout-ZCNMMHMY.js → logout-QMXDFU2X.js} +2 -2
  206. package/dist-cli/chunks/{maestro-SZTNKLDF.js → maestro-ZXU3YCVX.js} +3 -3
  207. package/dist-cli/chunks/{maestro-generate-DCFAIZ4H.js → maestro-generate-PYB5QY7K.js} +3 -3
  208. package/dist-cli/chunks/{mode-GRMQCRXR.js → mode-WGUCL5FZ.js} +2 -2
  209. package/dist-cli/chunks/{optional-demo-registry-W36EWFFB.js → optional-demo-registry-WH2O6H36.js} +2 -2
  210. package/dist-cli/chunks/{perf-QYBAAUZG.js → perf-TOD3UFAH.js} +2 -2
  211. package/dist-cli/chunks/{permissions-3QCQ6VF4.js → permissions-I5BRJGTB.js} +2 -2
  212. package/dist-cli/chunks/{record-QPPC2S4E.js → record-ZYL2FYSK.js} +3 -3
  213. package/dist-cli/chunks/{report-issue-7NMFP4HK.js → report-issue-TAI6DYZO.js} +2 -2
  214. package/dist-cli/chunks/reset-7YGYKQPQ.js +4 -0
  215. package/dist-cli/chunks/runtime-B4JO6QRI.js +4 -0
  216. package/dist-cli/chunks/{screenshot-command-67AECJFB.js → screenshot-command-7ANLODZY.js} +7 -7
  217. package/dist-cli/chunks/{screenshot-layers-ASWBYPJL.js → screenshot-layers-A7FYXSVU.js} +3 -3
  218. package/dist-cli/chunks/{screenshots-capture-PXA3HFQK.js → screenshots-capture-WT2ZY6CB.js} +2 -2
  219. package/dist-cli/chunks/serve-TG5WKA4V.js +44 -0
  220. package/dist-cli/chunks/{setup-7DWPMRSB.js → setup-PCWLD22V.js} +2 -2
  221. package/dist-cli/chunks/{skills-S3Y22TUA.js → skills-27ZHWCQS.js} +2 -2
  222. package/dist-cli/chunks/state-ZQVY46ZO.js +14 -0
  223. package/dist-cli/chunks/{storage-XUIMJWAJ.js → storage-OYC57C4X.js} +6 -6
  224. package/dist-cli/chunks/store-32HSPZHI.js +4 -0
  225. package/dist-cli/chunks/telemetry-PBJR7XHR.js +4 -0
  226. package/dist-cli/chunks/{timeline-TMPLQPSP.js → timeline-RAJJFQT6.js} +2 -2
  227. package/dist-cli/chunks/{upgrade-7HDSIM7K.js → upgrade-OEFNZRIP.js} +2 -2
  228. package/dist-cli/chunks/upload-LQJITLKK.js +4 -0
  229. package/dist-cli/chunks/version-NIXTY6PL.js +6 -0
  230. package/dist-cli/chunks/{web-DG3WBYD3.js → web-XBUTBVGR.js} +2 -2
  231. package/dist-cli/chunks/{what-happened-XFVUTZR7.js → what-happened-YNHRFUDX.js} +3 -3
  232. package/dist-lib/agent-daemon-client.cjs +1 -1
  233. package/dist-lib/agent-events.cjs +1 -1
  234. package/dist-lib/agent-identity.cjs +1 -1
  235. package/dist-lib/agent-sessions.cjs +1 -1
  236. package/dist-lib/attached-projects.cjs +1 -1
  237. package/dist-lib/auth/shared-session.cjs +1 -1
  238. package/dist-lib/backend-origin.cjs +1 -1
  239. package/dist-lib/beta.cjs +1 -1
  240. package/dist-lib/beta.mjs +1 -1
  241. package/dist-lib/bridge-constants.cjs +1 -1
  242. package/dist-lib/bridge-contract.cjs +20 -0
  243. package/dist-lib/cli-constants.cjs +1 -1
  244. package/dist-lib/config.cjs +1 -1
  245. package/dist-lib/detox/index.cjs +1 -1
  246. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  247. package/dist-lib/home-paths.cjs +67 -28
  248. package/dist-lib/host/bridge-host.cjs +140 -12
  249. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  250. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  251. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  252. package/dist-lib/host/websocket-proxy.cjs +1 -1
  253. package/dist-lib/index.cjs +2815 -40
  254. package/dist-lib/jump-to-source-babel.cjs +1 -1
  255. package/dist-lib/menu.cjs +1 -1
  256. package/dist-lib/menu.mjs +1 -1
  257. package/dist-lib/metro.cjs +1 -1
  258. package/dist-lib/profiles.cjs +1 -1
  259. package/dist-lib/public-brand.cjs +1 -1
  260. package/dist-lib/render-mode.cjs +1 -1
  261. package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
  262. package/dist-lib/sdk.cjs +2549 -2061
  263. package/dist-lib/sdk.mjs +2543 -2061
  264. package/dist-lib/skills.cjs +480 -280
  265. package/dist-lib/vite.cjs +1 -1
  266. package/package.json +8 -2
  267. package/src/bridge-constants.ts +3 -4
  268. package/src/bridge-contract.ts +251 -0
  269. package/src/connect.ts +83 -0
  270. package/src/disk-cleanup.ts +30 -0
  271. package/src/home-paths.ts +81 -38
  272. package/src/host/bridge-host.ts +134 -6
  273. package/src/index.ts +27 -1
  274. package/src/sdk.ts +8 -0
  275. package/src/sim-client.ts +660 -0
  276. package/dist-cli/chunks/camera-VL73YIKP.js +0 -22
  277. package/dist-cli/chunks/chunk-4NPPOV2N.js +0 -5
  278. package/dist-cli/chunks/chunk-FSUYIVJ6.js +0 -9
  279. package/dist-cli/chunks/chunk-G2WW6L2C.js +0 -23
  280. package/dist-cli/chunks/chunk-KTHV3RUS.js +0 -26
  281. package/dist-cli/chunks/chunk-LF2ZVT7O.js +0 -6
  282. package/dist-cli/chunks/chunk-NFK7T35W.js +0 -4
  283. package/dist-cli/chunks/chunk-TIVZIMMW.js +0 -4
  284. package/dist-cli/chunks/cli-version-WWLPBDQ7.js +0 -4
  285. package/dist-cli/chunks/daemon-G2ME7NLB.js +0 -4
  286. package/dist-cli/chunks/drivers-LDECZGP2.js +0 -4
  287. package/dist-cli/chunks/flow-UEQNVTU7.js +0 -4
  288. package/dist-cli/chunks/help-T5FYSVGB.js +0 -4
  289. package/dist-cli/chunks/home-paths-GT3LFNOR.js +0 -4
  290. package/dist-cli/chunks/inspect-ZA6XF5LD.js +0 -4
  291. package/dist-cli/chunks/install-desktop-TIMUDHPL.js +0 -4
  292. package/dist-cli/chunks/runtime-XOAXMSTU.js +0 -4
  293. package/dist-cli/chunks/serve-BI2NBAXG.js +0 -44
  294. package/dist-cli/chunks/store-JTHEJLAZ.js +0 -4
  295. package/dist-cli/chunks/telemetry-ZYJGD2DB.js +0 -4
  296. package/dist-cli/chunks/upload-GMSZPWM6.js +0 -4
  297. package/dist-cli/chunks/version-HOCHZ37L.js +0 -6
@@ -0,0 +1,453 @@
1
+ // sootsim maestro — drop-in replacement for the real `maestro` cli.
2
+ //
3
+ // users with an existing `.maestro/` directory can swap `maestro test`
4
+ // for `sootsim maestro test` and have their flows execute against
5
+ // sootsim's in-browser engine. the heavy lifting lives in `flow.ts` +
6
+ // `bridge-flow-runner.ts`; this file just re-maps the maestro argv shape
7
+ // onto the existing flow runner and handles directory iteration + init.
8
+
9
+ import * as fs from 'fs'
10
+ import * as path from 'path'
11
+ import yaml from 'yaml'
12
+ import { resolveCliAuth } from '../auth'
13
+ import { parseFlowFile } from '../flow-file'
14
+ import { registerRun, shouldRegisterRun, stepSummaryFromTrace } from '../run-registry'
15
+ import {
16
+ getLastFlowPreviewUploadResult,
17
+ getLastFlowTraceSteps,
18
+ hoistLeadingSimFlag,
19
+ runMaestroAuthoring,
20
+ runFlowPlayback,
21
+ } from './flow'
22
+ import { resolveDefaultUploadOrigin } from './upload'
23
+
24
+ export interface RunMaestroOptions {
25
+ port?: number
26
+ verbose?: boolean
27
+ }
28
+
29
+ const MAESTRO_DIRS = ['.maestro', 'maestro']
30
+
31
+ function printHelp() {
32
+ console.log(`
33
+ rnxsim maestro — author and run Maestro YAML flows against rnx
34
+
35
+ usage:
36
+ rnxsim maestro # discover .maestro/ or maestro/ in cwd, run all
37
+ rnxsim maestro test <flow-or-dir> # mirrors "maestro test"
38
+ rnxsim maestro test .maestro/ # every *.yaml / *.yml in a directory
39
+ rnxsim maestro generate "<goal>" # generate, run, and upload a Maestro flow
40
+ rnxsim maestro start|keep|end # author a flow from live CLI actions
41
+ rnxsim maestro validate <flow> # validate a Maestro YAML file
42
+ rnxsim maestro init # scaffold .maestro/login.yaml
43
+ rnxsim maestro --list-compat # print supported/unsupported verbs
44
+
45
+ options:
46
+ --env KEY=VALUE set env vars for \${KEY} interpolation (repeatable)
47
+ --continuous re-run the flow on file changes (alias for --watch)
48
+ --format <fmt> accepted for maestro cli compat (not used)
49
+ --new force a fresh browser window for this run
50
+ --headed force a fresh, VISIBLE window (implies --new,
51
+ overrides --headless) — watch the flow run live
52
+ --record record a webm while the flow runs
53
+ --preview record events+video, upload, and print /preview/<id>
54
+ --preview-open open the uploaded preview link after a successful run
55
+ --preview-origin <url> upload target for --preview
56
+ --preview-public-origin <url>
57
+ public link origin for --preview
58
+ --slow <ms> delay between steps for natural pacing
59
+
60
+ examples:
61
+ rnxsim maestro test .maestro/login.yaml
62
+ rnxsim maestro test .maestro/
63
+ rnxsim maestro generate "log in and verify the welcome screen"
64
+ rnxsim maestro start
65
+ rnxsim maestro end --output .maestro/login.yaml --validate
66
+ rnxsim maestro --env USERNAME=alice test .maestro/login.yaml
67
+ rnxsim maestro init
68
+ `)
69
+ }
70
+
71
+ function printCompatMatrix() {
72
+ console.log(`
73
+ rnxsim maestro — compatibility matrix
74
+
75
+ supported verbs:
76
+ launchApp, stopApp, clearState, clearKeychain (warn-only),
77
+ tapOn, tapAtCoords, longPressOn,
78
+ inputText, pressKey, dispatchKey, hideKeyboard, eraseText,
79
+ assertVisible, assertNotVisible, assertTreeContains,
80
+ waitFor, extendedWaitUntil, waitForAnimationToEnd,
81
+ scroll, scrollUntilVisible, scrollTo, swipe, pinch,
82
+ takeScreenshot, dumpTree, back,
83
+ repeat (times), runFlow (file / inline commands / env / when),
84
+ runScript (maestro JS: http, json(), output, env vars, console.log),
85
+ evalScript, copyTextFrom (\${maestro.copiedText}), openLink,
86
+ when: (visible / notVisible / platform / true),
87
+ optional:, label:, onFlowStart, onFlowComplete,
88
+ \${...} JS-expression interpolation at step execution time
89
+ (env vars as globals, output.*, maestro.*, \${EXPR || 'default'}).
90
+
91
+ workspace mode (directory runs):
92
+ config.yaml / config.yml discovery, flows: inclusion globs,
93
+ includeTags / excludeTags against flow frontmatter tags.
94
+
95
+ partial:
96
+ clearKeychain — warn-only, rnx has no keychain surface.
97
+ when.platform — rnx reports iOS.
98
+ openLink — dispatched into the app via the Linking emulation
99
+ (react-navigation deep links work); no OS-level Safari fallback.
100
+ launchApp.arguments — ignored; rnx has no native process.
101
+ launchApp.resetRuntime — sootsim-only extension for recorded benchmarks:
102
+ keep tenant storage/auth, but soft-reload the guest app inside launchApp.
103
+ takeScreenshot — maestro string form works unchanged; rnx also accepts
104
+ { path, withFrame } for framed export.
105
+ env — rnx seeds the JS env from the full shell environment, not just
106
+ MAESTRO_* (so \${SOOTSIM_*} credentials resolve); a bare \${NAME} that is
107
+ undefined fails the step loudly instead of typing "undefined".
108
+
109
+ not yet implemented (will throw "unsupported maestro verb"):
110
+ travel, setLocation, setAirplaneMode, killApp, faker,
111
+ addMedia, startRecording, stopRecording (use --record flag),
112
+ repeat.while (only repeat.times is supported).
113
+ `)
114
+ }
115
+
116
+ function looksLikeFlag(s: string): boolean {
117
+ return s.startsWith('-')
118
+ }
119
+
120
+ // extract --env KEY=VALUE pairs from argv and apply to process.env for the
121
+ // duration of the run. returns argv with the consumed flags removed so the
122
+ // rest can be forwarded to runFlowPlayback verbatim.
123
+ function applyEnvFlags(args: string[]): string[] {
124
+ const out: string[] = []
125
+ for (let i = 0; i < args.length; i++) {
126
+ if (args[i] === '--env' || args[i] === '-e') {
127
+ const kv = args[i + 1]
128
+ if (!kv || !kv.includes('=')) {
129
+ throw new Error(`--env expects KEY=VALUE (got ${JSON.stringify(kv ?? '')})`)
130
+ }
131
+ const eq = kv.indexOf('=')
132
+ const key = kv.slice(0, eq)
133
+ const value = kv.slice(eq + 1)
134
+ process.env[key] = value
135
+ i += 1
136
+ continue
137
+ }
138
+ out.push(args[i])
139
+ }
140
+ return out
141
+ }
142
+
143
+ // Remove Maestro-only flags that the shared runner does not know.
144
+ // these are accepted so users can literally s/maestro/sootsim maestro/ in
145
+ // their scripts without edits.
146
+ function stripUnknownMaestroFlags(args: string[]): string[] {
147
+ const out: string[] = []
148
+ for (let i = 0; i < args.length; i++) {
149
+ const a = args[i]
150
+ if (a === '--format' || a === '--output' || a === '--device' || a === '-d') {
151
+ i += 1
152
+ continue
153
+ }
154
+ if (a === '--continuous') {
155
+ // map to --watch for runFlowPlayback's existing watch path if one
156
+ // exists; today runFlowPlayback has no watch mode, so we drop the
157
+ // flag and log a warning rather than silently changing behavior.
158
+ console.warn(' warn: --continuous is not yet implemented in rnx — running once')
159
+ continue
160
+ }
161
+ out.push(a)
162
+ }
163
+ return out
164
+ }
165
+
166
+ function findDefaultMaestroTarget(cwd: string): string | null {
167
+ for (const dir of MAESTRO_DIRS) {
168
+ const abs = path.join(cwd, dir)
169
+ if (fs.existsSync(abs) && fs.statSync(abs).isDirectory()) return abs
170
+ }
171
+ return null
172
+ }
173
+
174
+ interface MaestroWorkspaceConfig {
175
+ flows?: string[]
176
+ includeTags?: string[]
177
+ excludeTags?: string[]
178
+ }
179
+
180
+ // workspace planning for directory targets — mirrors maestro's
181
+ // WorkspaceExecutionPlanner: discover config.yaml/config.yml in the
182
+ // directory, walk it recursively for *.yaml/*.yml flow files (config files
183
+ // excluded), keep the ones matching the config's `flows:` globs (default
184
+ // `*` = top level only), then filter by includeTags/excludeTags against
185
+ // each flow's frontmatter `tags:`.
186
+ export function collectFlowFiles(target: string): string[] {
187
+ const stat = fs.statSync(target)
188
+ if (stat.isFile()) return [target]
189
+ if (!stat.isDirectory()) {
190
+ throw new Error(`not a file or directory: ${target}`)
191
+ }
192
+
193
+ let config: MaestroWorkspaceConfig = {}
194
+ const configPath = ['config.yaml', 'config.yml']
195
+ .map((name) => path.join(target, name))
196
+ .find((p) => fs.existsSync(p))
197
+ if (configPath) {
198
+ // yaml.parse returns any; shape-check the fields we use below instead
199
+ // of trusting the file
200
+ const parsed = yaml.parse(fs.readFileSync(configPath, 'utf8'))
201
+ if (parsed && typeof parsed === 'object') {
202
+ config = parsed
203
+ }
204
+ }
205
+
206
+ const globs =
207
+ Array.isArray(config.flows) && config.flows.length > 0 ? config.flows : ['*']
208
+ const isFlowFile = (rel: string) => {
209
+ const base = path.basename(rel)
210
+ if (base.startsWith('.')) return false
211
+ if (!base.endsWith('.yaml') && !base.endsWith('.yml')) return false
212
+ if (base === 'config.yaml' || base === 'config.yml') return false
213
+ return true
214
+ }
215
+ const matched = new Set<string>()
216
+ for (const glob of globs) {
217
+ for (const rel of fs.globSync(glob, { cwd: target })) {
218
+ if (!isFlowFile(rel)) continue
219
+ const abs = path.join(target, rel)
220
+ if (fs.existsSync(abs) && fs.statSync(abs).isFile()) matched.add(abs)
221
+ }
222
+ }
223
+ let files = [...matched].sort()
224
+
225
+ const includeTags = Array.isArray(config.includeTags) ? config.includeTags : []
226
+ const excludeTags = Array.isArray(config.excludeTags) ? config.excludeTags : []
227
+ if (includeTags.length > 0 || excludeTags.length > 0) {
228
+ files = files.filter((file) => {
229
+ const { frontmatter } = parseFlowFile(fs.readFileSync(file, 'utf8'))
230
+ const tags: string[] = Array.isArray(frontmatter.tags) ? frontmatter.tags : []
231
+ if (includeTags.length > 0 && !tags.some((t) => includeTags.includes(t))) {
232
+ return false
233
+ }
234
+ if (tags.some((t) => excludeTags.includes(t))) return false
235
+ return true
236
+ })
237
+ }
238
+ return files
239
+ }
240
+
241
+ async function runInit(cwd: string): Promise<number> {
242
+ const dir = path.join(cwd, '.maestro')
243
+ fs.mkdirSync(dir, { recursive: true })
244
+ const outPath = path.join(dir, 'login.yaml')
245
+ if (fs.existsSync(outPath)) {
246
+ console.error(` error: ${outPath} already exists`)
247
+ return 1
248
+ }
249
+ const starter = `# rnxsim maestro starter flow — drop-in compatible with the maestro cli.
250
+ # run: rnxsim maestro test .maestro/login.yaml
251
+ appId: com.example.app
252
+ ---
253
+ - launchApp
254
+ - tapOn:
255
+ id: "email"
256
+ - inputText: "user@example.com"
257
+ - tapOn:
258
+ id: "password"
259
+ - inputText: "secret123"
260
+ - tapOn: "Sign in"
261
+ - assertVisible: "Welcome"
262
+ `
263
+ fs.writeFileSync(outPath, starter, 'utf8')
264
+ console.log(` + wrote ${outPath}`)
265
+ console.log(` next: rnxsim maestro test .maestro/login.yaml`)
266
+ return 0
267
+ }
268
+
269
+ export async function runMaestro(
270
+ args: string[],
271
+ opts: RunMaestroOptions = {},
272
+ ): Promise<number> {
273
+ if (args.includes('--help') || args.includes('-h')) {
274
+ printHelp()
275
+ return 0
276
+ }
277
+ if (args.includes('--list-compat')) {
278
+ printCompatMatrix()
279
+ return 0
280
+ }
281
+
282
+ let remaining: string[]
283
+ try {
284
+ remaining = applyEnvFlags(args)
285
+ } catch (err) {
286
+ console.error(` error: ${(err as Error).message}`)
287
+ return 1
288
+ }
289
+ // bin.ts prepends the global `--sim <id>` to argv. without hoisting it
290
+ // out, `remaining[0]` is `--sim` instead of the `test` subcommand, so
291
+ // maestro auto-discovered a default flow and forwarded `--sim`/`test`/
292
+ // the path as bogus flow args — the explicit sim was silently dropped
293
+ // (QA F21-4). hoist it to the tail so it still rides into forwardArgs →
294
+ // runFlowPlayback, where parseBridgeCliArgs sees simIdSource === 'flag'.
295
+ remaining = hoistLeadingSimFlag(remaining)
296
+
297
+ const cwd = process.cwd()
298
+ const sub = remaining[0]
299
+
300
+ if (sub === 'init') {
301
+ return runInit(cwd)
302
+ }
303
+ if (sub === 'generate') {
304
+ const { runMaestroGenerate } = await import('./maestro-generate')
305
+ return runMaestroGenerate(remaining.slice(1))
306
+ }
307
+ if (
308
+ sub === 'start' ||
309
+ sub === 'keep' ||
310
+ sub === 'good' ||
311
+ sub === 'end' ||
312
+ sub === 'validate'
313
+ ) {
314
+ return runMaestroAuthoring(remaining)
315
+ }
316
+ remaining = stripUnknownMaestroFlags(remaining)
317
+
318
+ // resolve the target flow file or directory.
319
+ let target: string | null = null
320
+ let forwardArgs: string[]
321
+
322
+ if (sub === 'test') {
323
+ // `sootsim maestro test <flow-or-dir> [flags...]`
324
+ const positional = remaining.slice(1).find((a) => !looksLikeFlag(a))
325
+ if (positional) {
326
+ target = path.resolve(cwd, positional)
327
+ forwardArgs = remaining.slice(1).filter((a) => a !== positional)
328
+ } else {
329
+ target = findDefaultMaestroTarget(cwd)
330
+ forwardArgs = remaining.slice(1)
331
+ }
332
+ } else if (sub && !looksLikeFlag(sub)) {
333
+ // `sootsim maestro <flow-or-dir>` — tolerate the shorthand.
334
+ target = path.resolve(cwd, sub)
335
+ forwardArgs = remaining.slice(1)
336
+ } else {
337
+ // no positional — auto-discover.
338
+ target = findDefaultMaestroTarget(cwd)
339
+ forwardArgs = remaining
340
+ }
341
+
342
+ if (!target) {
343
+ console.error(`
344
+ error: no maestro flows found.
345
+ expected one of: ${MAESTRO_DIRS.map((d) => `./${d}/`).join(', ')}
346
+ or pass a flow explicitly: rnxsim maestro test path/to/flow.yaml
347
+ or scaffold a starter flow: rnxsim maestro init
348
+ `)
349
+ return 1
350
+ }
351
+
352
+ if (!fs.existsSync(target)) {
353
+ console.error(` error: ${target} not found`)
354
+ return 1
355
+ }
356
+
357
+ let files: string[]
358
+ try {
359
+ files = collectFlowFiles(target)
360
+ } catch (err) {
361
+ console.error(` error: ${(err as Error).message}`)
362
+ return 1
363
+ }
364
+
365
+ if (files.length === 0) {
366
+ console.error(` error: no *.yaml or *.yml files found in ${target}`)
367
+ return 1
368
+ }
369
+
370
+ console.log(`
371
+ rnxsim maestro — ${files.length} flow${files.length === 1 ? '' : 's'}
372
+ root: ${path.relative(cwd, target) || '.'}
373
+ `)
374
+
375
+ // registration target for hosted run rows — resolved once per invocation.
376
+ const auth = resolveCliAuth()
377
+ const flagValue = (name: string) => {
378
+ const index = forwardArgs.indexOf(name)
379
+ return index >= 0 ? forwardArgs[index + 1] : undefined
380
+ }
381
+ // only `registerRun` below consumes this, and only for runs that register.
382
+ // resolving it up front probed https://contrast.localhost:3000 and blocked
383
+ // every flow — including ones that upload and register nothing — for the
384
+ // probe's full 2s timeout whenever no local Contrast stack was listening.
385
+ // resolve on first use instead, and keep the one probe per invocation.
386
+ let registerOriginPromise: Promise<string> | null = null
387
+ const registerOrigin = () =>
388
+ (registerOriginPromise ??= resolveDefaultUploadOrigin(
389
+ flagValue('--preview-origin') ?? flagValue('--origin'),
390
+ ))
391
+
392
+ let worstExit = 0
393
+ const results: Array<{ file: string; exit: number }> = []
394
+ for (const file of files) {
395
+ console.log(`\n ▶ ${path.relative(cwd, file)}`)
396
+ const playbackArgs = [file, ...forwardArgs]
397
+ // `--port` is the BRIDGE port everywhere else in the CLI, so forward it as
398
+ // one. sending it on as `--url` made it an app target instead, which left
399
+ // the playback bridge on whichever world resolveBridgeWorld picks by
400
+ // default (a live Vite dev shell selected from the development registry).
401
+ // so `sootsim --port <installed daemon> maestro …` silently ran the flow
402
+ // against the dev engine, then failed trying to resolve a bundle at the
403
+ // bridge port. an explicit command-level `--port` still wins, since
404
+ // parseBridgeCliArgs takes the last occurrence.
405
+ if (opts.port) {
406
+ playbackArgs.push('--port', String(opts.port))
407
+ }
408
+ let exit = 0
409
+ const startedAt = Date.now()
410
+ try {
411
+ exit = await runFlowPlayback(playbackArgs)
412
+ } catch (err) {
413
+ console.error(` x ${(err as Error).message}`)
414
+ exit = 1
415
+ }
416
+ results.push({ file, exit })
417
+ if (exit !== 0 && worstExit === 0) worstExit = exit
418
+
419
+ const upload = getLastFlowPreviewUploadResult()
420
+ if (shouldRegisterRun({ uploadedShare: Boolean(upload), auth })) {
421
+ const traceSteps = getLastFlowTraceSteps()
422
+ const failedStep = traceSteps.find((step) => step.status === 'failure')
423
+ const run = await registerRun({
424
+ origin: await registerOrigin(),
425
+ kind: 'maestro',
426
+ name: path.relative(cwd, file),
427
+ status: exit === 0 ? 'passed' : 'failed',
428
+ failureMessage: exit === 0 ? null : (failedStep?.error ?? 'flow playback failed'),
429
+ previewShareId: upload?.shareId ?? null,
430
+ owner: flagValue('--owner') ?? null,
431
+ repo: flagValue('--repo') ?? null,
432
+ durationMs: Date.now() - startedAt,
433
+ ...stepSummaryFromTrace(traceSteps),
434
+ auth,
435
+ })
436
+ if (run) {
437
+ console.log(` run: ${run.id}${run.traceUrl ? ` · replay: ${run.traceUrl}` : ''}`)
438
+ }
439
+ }
440
+ }
441
+
442
+ if (files.length > 1) {
443
+ console.log(`\n maestro summary:`)
444
+ for (const r of results) {
445
+ const label = r.exit === 0 ? 'pass' : 'fail'
446
+ console.log(` ${label} ${path.relative(cwd, r.file)}`)
447
+ }
448
+ const passed = results.filter((r) => r.exit === 0).length
449
+ console.log(` ${passed}/${results.length} passed`)
450
+ }
451
+
452
+ return worstExit
453
+ }
@@ -0,0 +1,57 @@
1
+ import { runScreenshotMode } from './screenshot-mode'
2
+ import { runThreeMode } from './three-mode'
3
+
4
+ interface ModeOptions {
5
+ port?: number
6
+ verbose?: boolean
7
+ }
8
+
9
+ const VALUE_FLAGS = new Set(['--sim', '--port', '-p'])
10
+ const SUBCOMMANDS = new Set(['screenshot', 'three'])
11
+
12
+ function findSubcommand(args: string[]): { name: string; index: number } | null {
13
+ for (let i = 0; i < args.length; i++) {
14
+ const arg = args[i]
15
+ if (arg.startsWith('-')) {
16
+ if (VALUE_FLAGS.has(arg)) i++
17
+ continue
18
+ }
19
+ return SUBCOMMANDS.has(arg) ? { name: arg, index: i } : null
20
+ }
21
+ return null
22
+ }
23
+
24
+ function withoutSubcommand(args: string[], index: number): string[] {
25
+ return [...args.slice(0, index), ...args.slice(index + 1)]
26
+ }
27
+
28
+ function printHelp(): void {
29
+ console.log(`
30
+ rnxsim mode — toggle live display modes
31
+
32
+ usage:
33
+ rnxsim mode screenshot [on|off|toggle]
34
+ rnxsim mode three [on|off|toggle]
35
+ rnxsim mode three configure [options]
36
+ `)
37
+ }
38
+
39
+ export async function runMode(args: string[], opts: ModeOptions): Promise<void> {
40
+ if (args.includes('--help') || args.includes('-h')) {
41
+ printHelp()
42
+ return
43
+ }
44
+
45
+ const sub = findSubcommand(args)
46
+ if (!sub) {
47
+ printHelp()
48
+ process.exit(1)
49
+ }
50
+
51
+ const rest = withoutSubcommand(args, sub.index)
52
+ if (sub.name === 'screenshot') {
53
+ await runScreenshotMode(rest, opts)
54
+ return
55
+ }
56
+ await runThreeMode(rest, opts)
57
+ }
@@ -0,0 +1,80 @@
1
+ // shared hint printed when the CLI has no bridge or no desktop companion.
2
+
3
+ import { findDesktopCompanion } from '../desktop-companion'
4
+ import type { BridgeSimInfo } from '../ws-bridge'
5
+
6
+ export function printMissingCompanionHint() {
7
+ console.error(' no rnxsim desktop companion found.')
8
+ console.error('')
9
+ console.error(' install the desktop app:')
10
+ console.error(' rnxsim desktop install')
11
+ console.error('')
12
+ console.error(' or run setup to install the background daemon:')
13
+ console.error(' rnxsim setup')
14
+ console.error('')
15
+ }
16
+
17
+ export function printMissingBridgeHint(port: number) {
18
+ const companion = findDesktopCompanion()
19
+ console.log(` note: no rnx bridge detected on port ${port}`)
20
+ if (companion) {
21
+ console.log(` launch the installed companion with:`)
22
+ console.log(` rnxsim desktop`)
23
+ return
24
+ }
25
+ console.log('')
26
+ console.log(' run `rnxsim setup` to install the background daemon,')
27
+ console.log(' or run `rnxsim serve` in another shell for a foreground bridge.')
28
+ }
29
+
30
+ export function printMissingSimHint(port: number) {
31
+ console.error('')
32
+ console.error(` no sim is connected to the rnx bridge on port ${port}.`)
33
+ console.error('')
34
+ console.error(' start your app dev server, then open it in rnx:')
35
+ console.error(' npx expo start --localhost --port 8081')
36
+ console.error(' rnxsim open 8081')
37
+ console.error('')
38
+ console.error(' then inspect the live app:')
39
+ console.error(' rnxsim describe')
40
+ }
41
+
42
+ // printed when a command targeted a specific `--sim <id>` that the bridge
43
+ // does not know — distinct from printMissingSimHint, which assumes the bridge
44
+ // has no sims at all. when other sims *are* connected, "no sim is connected"
45
+ // is a flat-out lie; list the valid ids instead so the user can pick one.
46
+ export async function printUnknownSimHint(
47
+ bridge: { listSims: () => Promise<BridgeSimInfo[]> },
48
+ port: number,
49
+ requestedId: string,
50
+ ) {
51
+ let sims: BridgeSimInfo[] = []
52
+ try {
53
+ sims = await bridge.listSims()
54
+ } catch {
55
+ // bridge unreachable while reporting — fall through to the generic hint
56
+ }
57
+ console.error('')
58
+ if (sims.length === 0) {
59
+ printMissingSimHint(port)
60
+ return
61
+ }
62
+ console.error(
63
+ ` no sim with id "${requestedId}" is connected to the bridge on port ${port}.`,
64
+ )
65
+ console.error('')
66
+ console.error(' connected sims:')
67
+ for (const sim of sims) {
68
+ const flags = [
69
+ sim.isPrimary ? 'primary' : null,
70
+ sim.readyState,
71
+ sim.userVisible === false ? 'hidden' : sim.userVisible === true ? 'visible' : null,
72
+ sim.userFocused ? 'focused' : null,
73
+ ]
74
+ .filter(Boolean)
75
+ .join(', ')
76
+ console.error(` ${sim.id} (${flags})`)
77
+ }
78
+ console.error('')
79
+ console.error(' pass a valid --sim id, or run `rnxsim list` to see all sessions.')
80
+ }
@@ -0,0 +1,66 @@
1
+ import { runCpuProfile } from './cpu-profile'
2
+ import { runInspect } from './inspect'
3
+ import { runReact } from './react'
4
+
5
+ interface PerfOptions {
6
+ port?: number
7
+ verbose?: boolean
8
+ }
9
+
10
+ const VALUE_FLAGS = new Set(['--sim', '--port', '-p'])
11
+ const SUBCOMMANDS = new Set(['cpu', 'react', 'shell', 'scroll'])
12
+
13
+ function findSubcommand(args: string[]): { name: string; index: number } | null {
14
+ for (let i = 0; i < args.length; i++) {
15
+ const arg = args[i]
16
+ if (arg.startsWith('-')) {
17
+ if (VALUE_FLAGS.has(arg)) i++
18
+ continue
19
+ }
20
+ return SUBCOMMANDS.has(arg) ? { name: arg, index: i } : null
21
+ }
22
+ return null
23
+ }
24
+
25
+ function withoutSubcommand(args: string[], index: number): string[] {
26
+ return [...args.slice(0, index), ...args.slice(index + 1)]
27
+ }
28
+
29
+ function printHelp(): void {
30
+ console.log(`
31
+ rnxsim perf — performance profiling tools
32
+
33
+ usage:
34
+ rnxsim perf cpu [options]
35
+ rnxsim perf react <subcommand> [options]
36
+ rnxsim perf shell <start|stop|transition> [options]
37
+ rnxsim perf scroll <start|stop> [options]
38
+
39
+ examples:
40
+ rnxsim perf cpu --duration 5 --output /tmp/trace.cpuprofile
41
+ rnxsim perf react summary --limit 10
42
+ rnxsim perf shell start
43
+ rnxsim perf shell stop --json
44
+ rnxsim perf scroll start
45
+ rnxsim perf scroll stop --json
46
+ `)
47
+ }
48
+
49
+ export async function runPerf(args: string[], opts: PerfOptions): Promise<number> {
50
+ const sub = findSubcommand(args)
51
+ if (!sub) {
52
+ printHelp()
53
+ return args.includes('--help') || args.includes('-h') ? 0 : 1
54
+ }
55
+
56
+ const rest = withoutSubcommand(args, sub.index)
57
+ if (sub.name === 'cpu') return runCpuProfile(rest, opts)
58
+ if (sub.name === 'react') return runReact(rest, opts)
59
+
60
+ const inspectArgs = rest.includes('--help') || rest.includes('-h') ? [] : rest
61
+ await runInspect(['debug', 'perf', ...inspectArgs], {
62
+ ...opts,
63
+ internalPerfCommand: sub.name === 'scroll' ? 'scroll' : 'shell',
64
+ })
65
+ return typeof process.exitCode === 'number' ? process.exitCode : 0
66
+ }