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,39 @@
1
+ import { runElectron } from './electron'
2
+ import { runInstallDesktop } from './install-desktop'
3
+
4
+ interface DesktopOptions {
5
+ port?: number
6
+ device?: string
7
+ }
8
+
9
+ function printHelp(): void {
10
+ console.log(`
11
+ rnxsim desktop — launch or install the desktop companion
12
+
13
+ usage:
14
+ rnxsim desktop [options]
15
+ rnxsim desktop install [options]
16
+
17
+ options:
18
+ --port <number> connect to a running dev server on this port
19
+ --profile <id> launch with an isolated persistent storage profile
20
+ --ephemeral launch with a temporary isolated storage profile
21
+ -y, --yes install without prompting
22
+ `)
23
+ }
24
+
25
+ export async function runDesktop(args: string[], opts: DesktopOptions): Promise<void> {
26
+ if (args.includes('--help') || args.includes('-h')) {
27
+ printHelp()
28
+ return
29
+ }
30
+
31
+ const sub = args.find((arg) => !arg.startsWith('-'))
32
+ if (sub === 'install') {
33
+ const index = args.indexOf(sub)
34
+ await runInstallDesktop([...args.slice(0, index), ...args.slice(index + 1)])
35
+ return
36
+ }
37
+
38
+ await runElectron(args, opts)
39
+ }
@@ -0,0 +1,197 @@
1
+ // project detection — finds project root, detects framework, scans monorepo workspaces
2
+
3
+ import { existsSync, readFileSync, readdirSync, statSync } from 'fs'
4
+ import { basename, dirname, join } from 'path'
5
+
6
+ export type Framework = 'one' | 'expo' | 'bare' | 'unknown'
7
+ export type PackageManager = 'bun' | 'pnpm' | 'npm' | 'yarn'
8
+ export type MonorepoType = 'turbo' | 'pnpm' | 'nx' | 'npm-workspaces' | 'single'
9
+
10
+ export interface DetectedProject {
11
+ dir: string
12
+ name: string
13
+ framework: Framework
14
+ hasViteConfig: boolean
15
+ hasMetroConfig: boolean
16
+ hasSootsimDependency: boolean
17
+ devCommand: string | null
18
+ }
19
+
20
+ export interface DetectedWorkspace {
21
+ root: string
22
+ type: MonorepoType
23
+ packageManager: PackageManager
24
+ apps: DetectedProject[]
25
+ }
26
+
27
+ export function findProjectRoot(cwd: string): string {
28
+ let dir = cwd
29
+ while (true) {
30
+ if (
31
+ existsSync(join(dir, 'turbo.json')) ||
32
+ existsSync(join(dir, 'nx.json')) ||
33
+ existsSync(join(dir, 'lerna.json')) ||
34
+ existsSync(join(dir, 'pnpm-workspace.yaml'))
35
+ ) {
36
+ return dir
37
+ }
38
+
39
+ const pkgPath = join(dir, 'package.json')
40
+ if (existsSync(pkgPath)) {
41
+ try {
42
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'))
43
+ if (pkg.workspaces) return dir
44
+ } catch {}
45
+ }
46
+
47
+ const parent = dirname(dir)
48
+ if (parent === dir) break
49
+ dir = parent
50
+ }
51
+ return cwd
52
+ }
53
+
54
+ export function detectPackageManager(root: string): PackageManager {
55
+ if (existsSync(join(root, 'bun.lock')) || existsSync(join(root, 'bun.lockb'))) {
56
+ return 'bun'
57
+ }
58
+ if (existsSync(join(root, 'pnpm-lock.yaml'))) return 'pnpm'
59
+ if (existsSync(join(root, 'yarn.lock'))) return 'yarn'
60
+ return 'npm'
61
+ }
62
+
63
+ export function detectMonorepoType(root: string): MonorepoType {
64
+ if (existsSync(join(root, 'turbo.json'))) return 'turbo'
65
+ if (existsSync(join(root, 'nx.json'))) return 'nx'
66
+ if (existsSync(join(root, 'pnpm-workspace.yaml'))) return 'pnpm'
67
+
68
+ const pkgPath = join(root, 'package.json')
69
+ if (existsSync(pkgPath)) {
70
+ try {
71
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'))
72
+ if (pkg.workspaces) return 'npm-workspaces'
73
+ } catch {}
74
+ }
75
+ return 'single'
76
+ }
77
+
78
+ export function detectFramework(deps: Record<string, string>): Framework {
79
+ if (deps.one) return 'one'
80
+ if (deps.expo) return 'expo'
81
+ if (deps['react-native']) return 'bare'
82
+ return 'unknown'
83
+ }
84
+
85
+ export function detectProject(dir: string): DetectedProject | null {
86
+ const pkgPath = join(dir, 'package.json')
87
+ if (!existsSync(pkgPath)) return null
88
+
89
+ let pkg: any
90
+ try {
91
+ pkg = JSON.parse(readFileSync(pkgPath, 'utf8'))
92
+ } catch {
93
+ return null
94
+ }
95
+
96
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies }
97
+ const framework = detectFramework(deps)
98
+
99
+ if (framework === 'unknown') return null
100
+
101
+ return {
102
+ dir,
103
+ name: pkg.name || basename(dir),
104
+ framework,
105
+ hasViteConfig:
106
+ existsSync(join(dir, 'vite.config.ts')) || existsSync(join(dir, 'vite.config.js')),
107
+ hasMetroConfig:
108
+ existsSync(join(dir, 'metro.config.js')) ||
109
+ existsSync(join(dir, 'metro.config.ts')),
110
+ hasSootsimDependency: Boolean(deps.sootsim),
111
+ devCommand: pkg.scripts?.dev || null,
112
+ }
113
+ }
114
+
115
+ function getWorkspaceDirs(root: string): string[] {
116
+ const dirs: string[] = []
117
+
118
+ const pnpmWs = join(root, 'pnpm-workspace.yaml')
119
+ if (existsSync(pnpmWs)) {
120
+ const content = readFileSync(pnpmWs, 'utf8')
121
+ const match = content.match(/packages:\s*\n((?:\s+-\s+.+\n?)+)/)
122
+ if (match) {
123
+ const lines = match[1].split('\n').filter(Boolean)
124
+ for (const line of lines) {
125
+ const glob = line.replace(/^\s*-\s*['"]?/, '').replace(/['"]?\s*$/, '')
126
+ if (glob) dirs.push(...expandGlob(root, glob))
127
+ }
128
+ }
129
+ return dirs
130
+ }
131
+
132
+ const pkgPath = join(root, 'package.json')
133
+ if (existsSync(pkgPath)) {
134
+ try {
135
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'))
136
+ const workspaces = Array.isArray(pkg.workspaces)
137
+ ? pkg.workspaces
138
+ : pkg.workspaces?.packages || []
139
+ for (const glob of workspaces) {
140
+ dirs.push(...expandGlob(root, glob))
141
+ }
142
+ } catch {}
143
+ }
144
+
145
+ return dirs
146
+ }
147
+
148
+ function expandGlob(root: string, pattern: string): string[] {
149
+ const clean = pattern.replace(/\/\*\*?$/, '').replace(/\*$/, '')
150
+ const baseDir = join(root, clean)
151
+
152
+ if (!existsSync(baseDir)) return []
153
+
154
+ try {
155
+ const entries = readdirSync(baseDir)
156
+ return entries
157
+ .map((entry) => join(baseDir, entry))
158
+ .filter((path) => {
159
+ try {
160
+ return statSync(path).isDirectory() && existsSync(join(path, 'package.json'))
161
+ } catch {
162
+ return false
163
+ }
164
+ })
165
+ } catch {
166
+ return []
167
+ }
168
+ }
169
+
170
+ export function scanWorkspace(root: string): DetectedWorkspace {
171
+ const type = detectMonorepoType(root)
172
+ const packageManager = detectPackageManager(root)
173
+
174
+ if (type === 'single') {
175
+ const project = detectProject(root)
176
+ return {
177
+ root,
178
+ type,
179
+ packageManager,
180
+ apps: project ? [project] : [],
181
+ }
182
+ }
183
+
184
+ const workspaceDirs = getWorkspaceDirs(root)
185
+ const apps: DetectedProject[] = []
186
+
187
+ const rootProject = detectProject(root)
188
+ if (rootProject) apps.push(rootProject)
189
+
190
+ for (const dir of workspaceDirs) {
191
+ if (dir === root) continue
192
+ const project = detectProject(dir)
193
+ if (project) apps.push(project)
194
+ }
195
+
196
+ return { root, type, packageManager, apps }
197
+ }
@@ -0,0 +1,385 @@
1
+ // sootsim detox — run existing detox suites against sootsim
2
+ //
3
+ // the command is a thin orchestration layer over `npx jest`:
4
+ // 1. resolves a test dir (cwd e2e|test/e2e|detox, or the flag)
5
+ // 2. makes sure a sootsim shell is reachable (auto-launches one if not)
6
+ // 3. spawns jest with our preset baked in so `import ... from 'detox'`
7
+ // resolves to the sootsim driver
8
+ //
9
+ // it deliberately shells out to the user's own jest so their jest version,
10
+ // transforms, and setupFiles keep working. the only thing we force is the
11
+ // moduleNameMapper for `^detox$`.
12
+
13
+ import { spawn } from 'child_process'
14
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync } from 'fs'
15
+ import { tmpdir } from 'os'
16
+ import { dirname, resolve, join, relative } from 'path'
17
+ import { rnxPublicBrand } from '../../src/public-brand'
18
+ import { resolveCliAuth } from '../auth'
19
+ import { registerRun, shouldRegisterRun } from '../run-registry'
20
+ import { discoverSootsimUrl } from './flow'
21
+ import { resolveDefaultUploadOrigin } from './upload'
22
+
23
+ // resolve the detox preset directory via the package's own subpath export.
24
+ function resolveDetoxDir(): string {
25
+ try {
26
+ const entry = import.meta.resolve(`${rnxPublicBrand.packageName}/detox`)
27
+ // import.meta.resolve returns a file:// URL — convert to a path
28
+ return dirname(entry.startsWith('file://') ? entry.slice(7) : entry)
29
+ } catch {
30
+ // fallback for source-layout dev (no compiled package.json exports)
31
+ return resolve(import.meta.dirname!, '..', '..', 'detox')
32
+ }
33
+ }
34
+
35
+ interface RunDetoxOpts {
36
+ port?: number
37
+ verbose?: boolean
38
+ }
39
+
40
+ const HELP = `
41
+ rnxsim detox — run detox-style tests against an rnx shell
42
+
43
+ usage:
44
+ rnxsim detox [testFiles...] run all tests under e2e/, test/e2e/, or detox/
45
+ rnxsim detox --config <path> use a specific jest config
46
+ rnxsim detox init scaffold sootsim-detox.config.cjs + sample test
47
+ rnxsim detox --watch jest watch mode
48
+ rnxsim detox --headed keep the rnx shell window visible
49
+ rnxsim detox -t <pattern> pass a jest --testNamePattern
50
+ rnxsim detox --no-launch skip auto-launching an rnx shell
51
+
52
+ rnxsim detox auto-launches an rnx shell if none is running on the
53
+ expected port. disable with --no-launch if you're managing the shell yourself.
54
+ `
55
+
56
+ export async function runDetox(args: string[], opts: RunDetoxOpts = {}): Promise<void> {
57
+ if (args.includes('--help') || args.includes('-h')) {
58
+ console.log(HELP)
59
+ process.exit(0)
60
+ }
61
+
62
+ if (args[0] === 'init') {
63
+ return initDetoxProject()
64
+ }
65
+
66
+ const getFlag = (name: string) => {
67
+ const i = args.indexOf(name)
68
+ return i >= 0 ? args[i + 1] : undefined
69
+ }
70
+ const hasFlag = (name: string) => args.includes(name)
71
+
72
+ const watch = hasFlag('--watch')
73
+ const headed = hasFlag('--headed')
74
+ const noLaunch = hasFlag('--no-launch')
75
+ const configFlag = getFlag('--config')
76
+ const port = opts.port || Number(process.env.SOOTSIM_PORT) || 5173
77
+
78
+ // positional args: test file paths passed directly to jest
79
+ const positional = args.filter((a, i) => {
80
+ if (a.startsWith('--') || a === '-t' || a === '-h') return false
81
+ const prev = args[i - 1]
82
+ if (
83
+ prev === '--config' ||
84
+ prev === '-t' ||
85
+ prev === '--testNamePattern' ||
86
+ prev === '--grep'
87
+ )
88
+ return false
89
+ return true
90
+ })
91
+ const passthroughJestFlags = args.filter(
92
+ (a) => a === '--runInBand' || a.startsWith('--maxWorkers='),
93
+ )
94
+
95
+ // test dir discovery. external test suites set SOOTSIM_TEST_DIR; otherwise
96
+ // we auto-detect the conventional locations.
97
+ const detoxDirs = ['e2e', 'test/e2e', 'detox']
98
+ let testDir: string | null = process.env.SOOTSIM_TEST_DIR
99
+ ? resolve(process.cwd(), process.env.SOOTSIM_TEST_DIR)
100
+ : null
101
+ if (!testDir) {
102
+ for (const dir of detoxDirs) {
103
+ const full = resolve(process.cwd(), dir)
104
+ if (existsSync(full)) {
105
+ testDir = full
106
+ break
107
+ }
108
+ }
109
+ }
110
+ if (!testDir) {
111
+ console.error(
112
+ ` error: no detox tests found. expected one of: ${detoxDirs.join(', ')}\n` +
113
+ ` run 'rnxsim detox init' to scaffold a starter suite.`,
114
+ )
115
+ process.exit(1)
116
+ }
117
+
118
+ // auto-launch: make sure a sootsim shell is reachable before running jest.
119
+ // Reuse the Maestro discovery path: scan local dev servers.
120
+ // and `sootsim open` the first match.
121
+ if (!noLaunch) {
122
+ await ensureShellRunning(port)
123
+ }
124
+
125
+ // build the jest command. prefer the user's config if they passed --config
126
+ // or if a sootsim-detox.config.cjs exists in cwd. otherwise point at our
127
+ // preset directly via --preset-free inline config.
128
+ const jestArgs: string[] = ['jest']
129
+ const localConfig =
130
+ configFlag ||
131
+ firstExisting(['sootsim-detox.config.cjs', 'jest.config.cjs', 'jest.config.js'])
132
+
133
+ let tempConfig: string | null = null
134
+ if (localConfig) {
135
+ jestArgs.push('--config', resolve(process.cwd(), localConfig))
136
+ } else {
137
+ // no local config — write a temp config that pulls in our preset.
138
+ // jest's --preset flag has quirky module resolution that breaks with
139
+ // subpath exports, so we generate a real config file instead.
140
+ // scope roots + testMatch to the discovered test dir so jest doesn't
141
+ // scan the entire repo (which causes haste collisions in monorepos).
142
+ const presetPath = resolve(resolveDetoxDir(), 'jest-preset.cjs')
143
+ tempConfig = join(tmpdir(), `sootsim-detox-${process.pid}.config.cjs`)
144
+ const cfg = {
145
+ rootDir: testDir,
146
+ roots: ['<rootDir>'],
147
+ testMatch: ['<rootDir>/**/*.test.ts', '<rootDir>/**/*.test.js'],
148
+ }
149
+ writeFileSync(
150
+ tempConfig,
151
+ `module.exports = { ...require(${JSON.stringify(presetPath)}), ...${JSON.stringify(cfg)} }\n`,
152
+ )
153
+ jestArgs.push('--config', tempConfig)
154
+ }
155
+
156
+ if (watch) jestArgs.push('--watch')
157
+
158
+ const testNamePattern =
159
+ getFlag('-t') || getFlag('--testNamePattern') || getFlag('--grep')
160
+ if (testNamePattern) jestArgs.push('-t', testNamePattern)
161
+
162
+ // hosted run registration (org dashboard pass/fail row). when it applies,
163
+ // capture jest's structured results via --json --outputFile so the row can
164
+ // carry test counts + the first failure; console output stays untouched.
165
+ const auth = resolveCliAuth()
166
+ const wantsRegistration = !watch && shouldRegisterRun({ uploadedShare: false, auth })
167
+ let resultsFile: string | null = process.env.SOOTSIM_JEST_OUTPUT_FILE || null
168
+ if (process.env.SOOTSIM_JEST_JSON === '1') jestArgs.push('--json')
169
+ if (process.env.SOOTSIM_JEST_OUTPUT_FILE) {
170
+ jestArgs.push('--outputFile', process.env.SOOTSIM_JEST_OUTPUT_FILE)
171
+ } else if (wantsRegistration) {
172
+ resultsFile = join(tmpdir(), `sootsim-detox-results-${process.pid}.json`)
173
+ jestArgs.push('--json', '--outputFile', resultsFile)
174
+ }
175
+
176
+ jestArgs.push(...passthroughJestFlags)
177
+
178
+ // any positional test files get passed straight through
179
+ jestArgs.push(...positional)
180
+
181
+ console.log(` rnxsim detox`)
182
+ console.log(` test dir: ${testDir}`)
183
+ console.log(` port: ${port}${headed ? ' (headed)' : ''}`)
184
+ if (localConfig) console.log(` config: ${localConfig}`)
185
+
186
+ const child = spawn('npx', jestArgs, {
187
+ cwd: process.cwd(),
188
+ stdio: 'inherit',
189
+ env: {
190
+ ...process.env,
191
+ SOOTSIM_TEST_DIR: testDir,
192
+ SOOTSIM_PORT: String(port),
193
+ SOOTSIM_HEADED: headed ? '1' : '',
194
+ SOOTSIM_URL: process.env.SOOTSIM_URL || `http://localhost:${port}`,
195
+ },
196
+ })
197
+
198
+ // jest owns the browsers, and it only reaps them from its own signal and exit
199
+ // handlers. this process is the one a caller has a pid for, so a SIGTERM aimed
200
+ // at `sootsim detox` lands here and jest never hears about it: it is a
201
+ // grandchild (this -> npx -> jest), so it keeps running with no parent and
202
+ // every chrome it launched is orphaned for good. forward the signal instead of
203
+ // exiting out from under it, and let its exit code end this process.
204
+ const forward = (signal: NodeJS.Signals) => () => {
205
+ child.kill(signal)
206
+ }
207
+ const onTerm = forward('SIGTERM')
208
+ const onInt = forward('SIGINT')
209
+ process.on('SIGTERM', onTerm)
210
+ process.on('SIGINT', onInt)
211
+
212
+ const startedAt = Date.now()
213
+ const code = await new Promise<number>((resolveCode, reject) => {
214
+ child.once('error', reject)
215
+ child.once('exit', (exitCode, signal) => {
216
+ if (signal) {
217
+ console.error(` error: jest terminated by ${signal}`)
218
+ resolveCode(1)
219
+ return
220
+ }
221
+ resolveCode(exitCode ?? 1)
222
+ })
223
+ })
224
+ process.off('SIGTERM', onTerm)
225
+ process.off('SIGINT', onInt)
226
+ if (tempConfig)
227
+ try {
228
+ unlinkSync(tempConfig)
229
+ } catch {}
230
+
231
+ if (wantsRegistration) {
232
+ const results = resultsFile ? readJestResults(resultsFile) : null
233
+ const origin = await resolveDefaultUploadOrigin()
234
+ const run = await registerRun({
235
+ origin,
236
+ kind: 'detox',
237
+ name: relative(process.cwd(), testDir) || 'e2e',
238
+ status: code === 0 ? 'passed' : 'failed',
239
+ summary: results
240
+ ? `${results.numPassedTests}/${results.numTotalTests} tests passed`
241
+ : null,
242
+ failureMessage: code === 0 ? null : (results?.firstFailure ?? null),
243
+ // tests map onto the row's step summary: total count + first failing
244
+ // index, same shape flow lanes fill from their step trace.
245
+ stepCount: results?.numTotalTests ?? null,
246
+ failedStepIndex: results?.firstFailedIndex ?? null,
247
+ durationMs: Date.now() - startedAt,
248
+ auth,
249
+ })
250
+ if (run) console.log(` run: ${run.id}`)
251
+ }
252
+ process.exit(code)
253
+ }
254
+
255
+ type JestRunSummary = {
256
+ numTotalTests: number
257
+ numPassedTests: number
258
+ firstFailure: string | null
259
+ firstFailedIndex: number | null
260
+ }
261
+
262
+ // parse jest's --json output file down to the counts + first failure the
263
+ // hosted run row carries. tolerant: any parse problem returns null and the
264
+ // run registers with exit-code status only.
265
+ function readJestResults(file: string): JestRunSummary | null {
266
+ try {
267
+ const parsed = JSON.parse(readFileSync(file, 'utf8')) as {
268
+ numTotalTests?: number
269
+ numPassedTests?: number
270
+ testResults?: Array<{
271
+ testResults?: Array<{
272
+ status?: string
273
+ fullName?: string
274
+ failureMessages?: string[]
275
+ }>
276
+ }>
277
+ }
278
+ let firstFailure: string | null = null
279
+ let firstFailedIndex: number | null = null
280
+ let index = 0
281
+ for (const suite of parsed.testResults ?? []) {
282
+ for (const test of suite.testResults ?? []) {
283
+ if (firstFailedIndex == null && test.status === 'failed') {
284
+ firstFailedIndex = index
285
+ const message = test.failureMessages?.[0]?.split('\n')[0]
286
+ firstFailure = [test.fullName, message].filter(Boolean).join(': ') || null
287
+ }
288
+ index += 1
289
+ }
290
+ }
291
+ if (typeof parsed.numTotalTests !== 'number') return null
292
+ return {
293
+ numTotalTests: parsed.numTotalTests,
294
+ numPassedTests: parsed.numPassedTests ?? 0,
295
+ firstFailure,
296
+ firstFailedIndex,
297
+ }
298
+ } catch {
299
+ return null
300
+ } finally {
301
+ if (!process.env.SOOTSIM_JEST_OUTPUT_FILE) {
302
+ try {
303
+ unlinkSync(file)
304
+ } catch {}
305
+ }
306
+ }
307
+ }
308
+
309
+ function firstExisting(names: string[]): string | null {
310
+ for (const n of names) {
311
+ if (existsSync(resolve(process.cwd(), n))) return n
312
+ }
313
+ return null
314
+ }
315
+
316
+ async function ensureShellRunning(port: number): Promise<void> {
317
+ const shellUrl = await discoverSootsimUrl()
318
+ if (shellUrl) return
319
+
320
+ console.warn(
321
+ ` warn: no rnx shell reachable on :${port}. start your app, then run \`rnxsim open <app-port>\`\n` +
322
+ ` or pass --no-launch if you're managing the shell yourself.`,
323
+ )
324
+ }
325
+
326
+ function initDetoxProject(): void {
327
+ const cwd = process.cwd()
328
+ const configPath = resolve(cwd, 'sootsim-detox.config.cjs')
329
+ if (existsSync(configPath)) {
330
+ console.log(` skip: ${configPath} already exists`)
331
+ } else {
332
+ writeFileSync(
333
+ configPath,
334
+ `// rnxsim detox jest config.
335
+ // extends the sootsim preset, which rewrites \`import ... from 'detox'\`
336
+ // to the sootsim driver and keeps your jest transforms intact.
337
+
338
+ /** @type {import('@jest/types').Config.InitialOptions} */
339
+ module.exports = {
340
+ preset: 'rnxsim/detox/jest-preset',
341
+ rootDir: __dirname,
342
+ testMatch: ['<rootDir>/e2e/**/*.test.ts', '<rootDir>/e2e/**/*.test.js'],
343
+ }
344
+ `,
345
+ )
346
+ console.log(` created ${configPath}`)
347
+ }
348
+
349
+ const e2eDir = resolve(cwd, 'e2e')
350
+ const samplePath = resolve(e2eDir, 'example.test.ts')
351
+ if (existsSync(samplePath)) {
352
+ console.log(` skip: ${samplePath} already exists`)
353
+ } else {
354
+ mkdirSync(e2eDir, { recursive: true })
355
+ writeFileSync(
356
+ samplePath,
357
+ `// sample rnxsim detox test. resolve \`detox\` via the jest preset in
358
+ // sootsim-detox.config.cjs — no extra imports required.
359
+
360
+ import { by, device, element, expect, waitFor } from 'detox'
361
+
362
+ describe('example', () => {
363
+ beforeAll(async () => {
364
+ await device.launchApp()
365
+ })
366
+
367
+ it('shows the welcome text', async () => {
368
+ await waitFor(element(by.text('Welcome'))).toBeVisible().withTimeout(5000)
369
+ })
370
+
371
+ it('taps a button by id', async () => {
372
+ await element(by.id('start-button')).tap()
373
+ await expect(element(by.id('home-screen'))).toBeVisible()
374
+ })
375
+ })
376
+ `,
377
+ )
378
+ console.log(` created ${samplePath}`)
379
+ }
380
+
381
+ console.log(`\n next:`)
382
+ console.log(` rnxsim detox # run the sample`)
383
+ console.log(` rnxsim detox --watch`)
384
+ process.exit(0)
385
+ }