rnxsim 0.1.312 → 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 (299) 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-NXVDDCFG.js → agent-7YBDCYMA.js} +2 -2
  126. package/dist-cli/chunks/{agent-wrapper-DXJCFKL3.js → agent-wrapper-2GHFBHCR.js} +2 -2
  127. package/dist-cli/chunks/{app-fonts-W5AJZQJF.js → app-fonts-RSNVPQSU.js} +2 -2
  128. package/dist-cli/chunks/{assert-3LOJEUDT.js → assert-XCMX3XJX.js} +2 -2
  129. package/dist-cli/chunks/{auth-MFTYYPNX.js → auth-B442HRAX.js} +2 -2
  130. package/dist-cli/chunks/{beta-GT3NN67B.js → beta-XJ55JK3M.js} +2 -2
  131. package/dist-cli/chunks/camera-UGYSSLIK.js +33 -0
  132. package/dist-cli/chunks/{chunk-EUAVBTKU.js → chunk-277AEQZX.js} +2 -2
  133. package/dist-cli/chunks/{chunk-5OX7ANRA.js → chunk-2JNSK774.js} +2 -2
  134. package/dist-cli/chunks/{chunk-L24Z3CRS.js → chunk-32WOTSTR.js} +3 -3
  135. package/dist-cli/chunks/{chunk-BKNOLP3J.js → chunk-3S753SNQ.js} +2 -2
  136. package/dist-cli/chunks/{chunk-N6A3SXMR.js → chunk-46ZOLOYA.js} +2 -2
  137. package/dist-cli/chunks/{chunk-GZDHEONW.js → chunk-5L7ELDQL.js} +8 -9
  138. package/dist-cli/chunks/{chunk-YZRFF4YA.js → chunk-7OOPFSQS.js} +2 -2
  139. package/dist-cli/chunks/{chunk-TBMVBI26.js → chunk-7SV3RPRW.js} +2 -2
  140. package/dist-cli/chunks/{chunk-JG6S4RSH.js → chunk-7ZC35MOU.js} +1 -1
  141. package/dist-cli/chunks/chunk-AMG5E6CC.js +9 -0
  142. package/dist-cli/chunks/{chunk-MENV7VLD.js → chunk-APWNH3A4.js} +1 -1
  143. package/dist-cli/chunks/chunk-B57XUKY3.js +4 -0
  144. package/dist-cli/chunks/{chunk-3D57IBPC.js → chunk-C2NL26TD.js} +1 -1
  145. package/dist-cli/chunks/chunk-D2FNUWAB.js +15 -0
  146. package/dist-cli/chunks/{chunk-4BXCZHAU.js → chunk-E5T4XSJ3.js} +2 -2
  147. package/dist-cli/chunks/{chunk-FTC4M26Q.js → chunk-EAC34EQS.js} +1 -1
  148. package/dist-cli/chunks/{chunk-NPVGMH2N.js → chunk-EG32ML36.js} +2 -2
  149. package/dist-cli/chunks/{chunk-DM6KUAOZ.js → chunk-FXUAC6D5.js} +1 -1
  150. package/dist-cli/chunks/chunk-GQSL4USA.js +6 -0
  151. package/dist-cli/chunks/{chunk-U4PN6JOT.js → chunk-HAXW27SS.js} +2 -2
  152. package/dist-cli/chunks/{chunk-ZMZTHU7V.js → chunk-IZAHPAN6.js} +1 -1
  153. package/dist-cli/chunks/{chunk-SI3BUTQR.js → chunk-J62KM5TB.js} +2 -2
  154. package/dist-cli/chunks/{chunk-Y2FSZSO7.js → chunk-JEMCD5E4.js} +1 -1
  155. package/dist-cli/chunks/{chunk-T4FT6CM7.js → chunk-JZS3Q37N.js} +2 -2
  156. package/dist-cli/chunks/{chunk-2SA2VTGP.js → chunk-KR4JON7D.js} +1 -1
  157. package/dist-cli/chunks/chunk-KWCYKQIQ.js +4 -0
  158. package/dist-cli/chunks/{chunk-HG7ORJMT.js → chunk-MCWPL644.js} +2 -2
  159. package/dist-cli/chunks/chunk-MJYK3N2I.js +4 -0
  160. package/dist-cli/chunks/{chunk-YRV7HR3P.js → chunk-O6TRIZNS.js} +2 -2
  161. package/dist-cli/chunks/{chunk-WBT32R6N.js → chunk-P7XL2E73.js} +3 -3
  162. package/dist-cli/chunks/{chunk-MNGQ42QE.js → chunk-PFQTUKQ4.js} +62 -87
  163. package/dist-cli/chunks/{chunk-Q3RGLERJ.js → chunk-PG5RZCTN.js} +2 -2
  164. package/dist-cli/chunks/{chunk-OR3XTXEB.js → chunk-QGRI2Z4M.js} +2 -2
  165. package/dist-cli/chunks/chunk-QLXXE7GE.js +125 -0
  166. package/dist-cli/chunks/{chunk-KZW5WBGU.js → chunk-QOJJJWJE.js} +89 -133
  167. package/dist-cli/chunks/{chunk-BRIZOR4K.js → chunk-RWZY5427.js} +2 -2
  168. package/dist-cli/chunks/{chunk-TYE3WBSR.js → chunk-RZKU2K3J.js} +2 -2
  169. package/dist-cli/chunks/{chunk-7VWOSTD5.js → chunk-SBV4IK4H.js} +1 -1
  170. package/dist-cli/chunks/{chunk-FWYYCUAZ.js → chunk-SGMVFFMK.js} +1 -1
  171. package/dist-cli/chunks/{chunk-LDQ7HVCW.js → chunk-TAX4UT2N.js} +1 -1
  172. package/dist-cli/chunks/{chunk-G74YIEVS.js → chunk-TUOFAWXT.js} +1 -1
  173. package/dist-cli/chunks/{chunk-274UD5FD.js → chunk-UHZLOHGP.js} +1 -1
  174. package/dist-cli/chunks/{chunk-37I5AIS7.js → chunk-VC7V76U3.js} +1 -1
  175. package/dist-cli/chunks/{chunk-B7KMNHX7.js → chunk-VQVMLW4U.js} +1 -1
  176. package/dist-cli/chunks/{chunk-VZBLFMYC.js → chunk-VUKKYPZN.js} +2 -2
  177. package/dist-cli/chunks/{chunk-VUFRB347.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-TCWQJILS.js → chunk-XULEACM4.js} +2 -2
  181. package/dist-cli/chunks/{chunk-XRGEEPJ5.js → chunk-YFSDM7AX.js} +4 -4
  182. package/dist-cli/chunks/chunk-YWI3UEVX.js +5 -0
  183. package/dist-cli/chunks/{chunk-TGQNQ6XT.js → chunk-ZBSJO4NB.js} +10 -9
  184. package/dist-cli/chunks/{cleanup-H3GWQB63.js → cleanup-EYLGCA6Z.js} +2 -2
  185. package/dist-cli/chunks/cli-version-LL2UGIHE.js +4 -0
  186. package/dist-cli/chunks/{compat-5WG2XJV7.js → compat-TLJYHB4E.js} +2 -2
  187. package/dist-cli/chunks/{config-ANMBVVLL.js → config-4JWUOEXK.js} +2 -2
  188. package/dist-cli/chunks/{control-NSGNQFJG.js → control-HPAOYF4N.js} +2 -2
  189. package/dist-cli/chunks/daemon-OYLASXLE.js +4 -0
  190. package/dist-cli/chunks/{debug-CHV2O2KF.js → debug-4BKXF6KI.js} +5 -5
  191. package/dist-cli/chunks/{desktop-FIPIK4CH.js → desktop-ZQ6ZD2S6.js} +3 -3
  192. package/dist-cli/chunks/{detox-CVUJP7K6.js → detox-WNPASSS3.js} +2 -2
  193. package/dist-cli/chunks/{device-6WCUM4W6.js → device-BXLXVG3V.js} +2 -2
  194. package/dist-cli/chunks/{diagnose-I6EMWEII.js → diagnose-3QX7W5RW.js} +2 -2
  195. package/dist-cli/chunks/{disk-cleanup-P27CYFTN.js → disk-cleanup-KSWKI7WB.js} +2 -2
  196. package/dist-cli/chunks/drivers-EPIEFF7P.js +4 -0
  197. package/dist-cli/chunks/{film-UOQFFYNF.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-NJYFSJKZ.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-BPFQENCX.js → login-IJAPUZHI.js} +4 -4
  205. package/dist-cli/chunks/{logout-5A45ZGY7.js → logout-QMXDFU2X.js} +2 -2
  206. package/dist-cli/chunks/{maestro-E4CSDMLM.js → maestro-ZXU3YCVX.js} +3 -3
  207. package/dist-cli/chunks/{maestro-generate-CKMOJQX2.js → maestro-generate-PYB5QY7K.js} +3 -3
  208. package/dist-cli/chunks/{mode-SZYNMIQF.js → mode-WGUCL5FZ.js} +2 -2
  209. package/dist-cli/chunks/{optional-demo-registry-FRH4SFVG.js → optional-demo-registry-WH2O6H36.js} +2 -2
  210. package/dist-cli/chunks/{perf-VXU2XJAX.js → perf-TOD3UFAH.js} +2 -2
  211. package/dist-cli/chunks/{permissions-IRIAOVND.js → permissions-I5BRJGTB.js} +2 -2
  212. package/dist-cli/chunks/{record-RY7FPLLH.js → record-ZYL2FYSK.js} +3 -3
  213. package/dist-cli/chunks/{report-issue-KNWXOE53.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-QLALR6GY.js → screenshot-command-7ANLODZY.js} +7 -7
  217. package/dist-cli/chunks/{screenshot-layers-OQFMFVRL.js → screenshot-layers-A7FYXSVU.js} +3 -3
  218. package/dist-cli/chunks/{screenshots-capture-6TSRJFGB.js → screenshots-capture-WT2ZY6CB.js} +2 -2
  219. package/dist-cli/chunks/serve-TG5WKA4V.js +44 -0
  220. package/dist-cli/chunks/{setup-7V6UKX4U.js → setup-PCWLD22V.js} +2 -2
  221. package/dist-cli/chunks/{skills-QQBOSETH.js → skills-27ZHWCQS.js} +2 -2
  222. package/dist-cli/chunks/state-ZQVY46ZO.js +14 -0
  223. package/dist-cli/chunks/{storage-TAPMKK5O.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-IAM564NA.js → timeline-RAJJFQT6.js} +2 -2
  227. package/dist-cli/chunks/{upgrade-CD55QXIX.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-6NW6YLPI.js → web-XBUTBVGR.js} +2 -2
  231. package/dist-cli/chunks/{what-happened-CZZDOOVG.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 +493 -282
  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-6EAK7CUL.js +0 -22
  277. package/dist-cli/chunks/chunk-33FKQH54.js +0 -5
  278. package/dist-cli/chunks/chunk-66NF2KNG.js +0 -9
  279. package/dist-cli/chunks/chunk-6UKJB2VC.js +0 -125
  280. package/dist-cli/chunks/chunk-7INBNA3U.js +0 -26
  281. package/dist-cli/chunks/chunk-AY47WKS5.js +0 -23
  282. package/dist-cli/chunks/chunk-HMDOLCKG.js +0 -15
  283. package/dist-cli/chunks/chunk-KR5BGMBZ.js +0 -4
  284. package/dist-cli/chunks/chunk-NLEMQ4PY.js +0 -6
  285. package/dist-cli/chunks/chunk-PEPUGJVI.js +0 -4
  286. package/dist-cli/chunks/cli-version-3QPAJ3LC.js +0 -4
  287. package/dist-cli/chunks/daemon-JHF4BJ3U.js +0 -4
  288. package/dist-cli/chunks/drivers-HQ6AKZT6.js +0 -4
  289. package/dist-cli/chunks/flow-N27MRRNL.js +0 -4
  290. package/dist-cli/chunks/help-SF6FEGEE.js +0 -4
  291. package/dist-cli/chunks/home-paths-XOV44JEF.js +0 -4
  292. package/dist-cli/chunks/inspect-CWFRFQTY.js +0 -4
  293. package/dist-cli/chunks/install-desktop-KLQDNXZQ.js +0 -4
  294. package/dist-cli/chunks/runtime-AR3GJTGK.js +0 -4
  295. package/dist-cli/chunks/serve-F54J2NQO.js +0 -44
  296. package/dist-cli/chunks/store-7NX3S43H.js +0 -4
  297. package/dist-cli/chunks/telemetry-E7BUK6RA.js +0 -4
  298. package/dist-cli/chunks/upload-GZ6LIMYM.js +0 -4
  299. package/dist-cli/chunks/version-3DYJ5AS3.js +0 -6
@@ -0,0 +1,65 @@
1
+ // driver registry — hardcoded list of known drivers plus lookup helpers.
2
+ // kept intentionally flat: no DI, no plugin discovery, no lazy imports. both
3
+ // built-in drivers load unconditionally because their module bodies are cheap
4
+ // (no I/O, no side effects).
5
+
6
+ import { electronDriver } from './electron'
7
+ import { playwrightDriver } from './playwright'
8
+ import type { Driver, DriverId } from './types'
9
+
10
+ export const ALL_DRIVERS: readonly Driver[] = [electronDriver, playwrightDriver]
11
+
12
+ export function getAllDrivers(): readonly Driver[] {
13
+ return ALL_DRIVERS
14
+ }
15
+
16
+ export function getDriver(id: DriverId): Driver | null {
17
+ return ALL_DRIVERS.find((d) => d.id === id) ?? null
18
+ }
19
+
20
+ // returns the first driver in preference order that reports availability.
21
+ // used by launch sites that want "pick whatever works" behavior when the
22
+ // user didn't pass an explicit --driver flag. if `forcedId` is given, that
23
+ // driver is returned even if unavailable so the caller can surface a
24
+ // specific error message rather than silently falling back.
25
+ export function resolveDriver(
26
+ forcedId: string | null | undefined,
27
+ preference: readonly DriverId[],
28
+ ): Driver | null {
29
+ if (forcedId) {
30
+ const match = ALL_DRIVERS.find((d) => d.id === forcedId)
31
+ return match ?? null
32
+ }
33
+ for (const id of preference) {
34
+ const driver = ALL_DRIVERS.find((d) => d.id === id)
35
+ if (driver && driver.availability().available) return driver
36
+ }
37
+ return null
38
+ }
39
+
40
+ // utility for the `sootsim list --drivers` view. returns a rich row per
41
+ // driver with availability + kind + description, suitable for a table.
42
+ export interface DriverListRow {
43
+ id: DriverId
44
+ name: string
45
+ kind: Driver['kind']
46
+ description: string
47
+ available: boolean
48
+ reason: string | null
49
+ detail: string | null
50
+ }
51
+
52
+ export function buildDriverListRows(): DriverListRow[] {
53
+ return ALL_DRIVERS.map((d) => {
54
+ const probe = d.availability()
55
+ return {
56
+ id: d.id,
57
+ name: d.name,
58
+ kind: d.kind,
59
+ description: d.description,
60
+ available: probe.available,
61
+ reason: probe.reason,
62
+ detail: probe.detail ?? null,
63
+ }
64
+ })
65
+ }
@@ -0,0 +1,102 @@
1
+ // driver / runner abstraction for sootsim. browser sims run in an isolated
2
+ // playwright-owned profile. electron remains the native desktop surface.
3
+
4
+ export type DriverId = 'electron' | 'playwright'
5
+
6
+ // broad category for grouping / filtering. "native" is the electron host;
7
+ // "automation" is the isolated playwright browser.
8
+ export type DriverKind = 'native' | 'automation'
9
+
10
+ export interface DriverAvailability {
11
+ // true when the driver can attempt a launch on this machine. a driver is
12
+ // allowed to be available but still fail at runtime (network issues,
13
+ // permission denied, ...). false when there's a hard precondition missing
14
+ // (binary not installed, package not resolvable).
15
+ available: boolean
16
+ // short free-text explaining why a driver is unavailable. null when
17
+ // `available` is true, or when no reason is known.
18
+ reason: string | null
19
+ // optional human-readable detail shown in verbose listings — e.g. the
20
+ // resolved binary path, version string, or install location.
21
+ detail?: string
22
+ }
23
+
24
+ export interface DriverLaunchOptions {
25
+ // shell URL to load in the launched process. optional because the electron
26
+ // driver can launch the companion app with
27
+ // no URL (it picks its last window). other drivers return launched:false
28
+ // with a helpful message when url is missing.
29
+ url?: string
30
+ // run in headless mode where the driver supports it. ignored by electron.
31
+ headless?: boolean
32
+ // initial device model seed forwarded to the sootsim shell. the electron
33
+ // driver forwards it via argv (--device=<model>); browser drivers receive
34
+ // it already stamped on the URL (?device=<model>) by runOpenCommand.
35
+ device?: string
36
+ // page viewport for browser-based drivers. runOpenCommand sizes this from
37
+ // the device profile (device pt size + frame chrome) so the sim isn't
38
+ // letterboxed inside playwright's 1280x720 default.
39
+ viewport?: { width: number; height: number }
40
+ // let the caller override the port used in availability / attach
41
+ // heuristics. most drivers don't need this.
42
+ port?: number
43
+ // when true, the launch is detached from the parent process and the
44
+ // CLI returns immediately. default true.
45
+ detached?: boolean
46
+ // named persistent storage profile. honored by drivers that own their
47
+ // browser context (electron, playwright). plain browser tabs reject it.
48
+ profileId?: string
49
+ // unnamed temporary storage. honored by electron/playwright.
50
+ ephemeralProfile?: boolean
51
+ // stable pid for the session that owns a CLI-opened host. electron closes
52
+ // only that session's windows when the owner exits; playwright requires it
53
+ // so a detached browser can never outlive its session. omitted only for the
54
+ // persistent `sootsim desktop` app lifecycle.
55
+ ownerPid?: number
56
+ // expose Chrome's remote-debugging endpoint on this port so a
57
+ // bridge-driven sim can also be cpu-profiled. only the playwright driver
58
+ // honors it (launches Chromium with --remote-debugging-port).
59
+ cdpPort?: number
60
+ // maximum time the detached host should wait for the launched sim to
61
+ // register with the bridge before it tears itself down.
62
+ connectTimeoutMs?: number
63
+ // the sootsim WS bridge port the launched sim page should register with,
64
+ // injected as window.__sootsimBridgePort. load-bearing when the dev bridge
65
+ // drifted off the shell-port-implied default (e.g. :7668 taken at the shell's
66
+ // boot -> bridge on :7669 while the shell kept :5173): the page's own
67
+ // shell-port heuristic would compute the stale :7668 and never connect.
68
+ bridgePort?: number
69
+ }
70
+
71
+ export interface DriverLaunchResult {
72
+ // true when the process was successfully spawned.
73
+ launched: boolean
74
+ // free-text describing what happened, used for CLI output and skill docs.
75
+ message: string
76
+ // optional handle details — currently free-shaped so each driver can
77
+ // expose what makes sense for its environment. pid for spawned processes,
78
+ // `target` for an install path, `attachUrl` for automation drivers.
79
+ pid?: number
80
+ target?: string
81
+ attachUrl?: string
82
+ connectAckFile?: string
83
+ diagnosticLogPath?: string
84
+ }
85
+
86
+ export interface Driver {
87
+ id: DriverId
88
+ // short human name shown in `sootsim list --drivers`.
89
+ name: string
90
+ // one-line description. keep under 80 chars so the list command reads
91
+ // nicely at default terminal width.
92
+ description: string
93
+ kind: DriverKind
94
+ // probe the host environment for this driver. cheap and synchronous —
95
+ // anything expensive belongs in `launch`.
96
+ availability(): DriverAvailability
97
+ // start the driver pointed at `opts.url`. may be async because some
98
+ // drivers (electron, playwright) do real I/O. must never throw on a
99
+ // benign "not installed" condition — return { launched: false } with a
100
+ // helpful message instead.
101
+ launch(opts: DriverLaunchOptions): Promise<DriverLaunchResult>
102
+ }
@@ -0,0 +1,142 @@
1
+ import * as fs from 'fs'
2
+ import yaml from 'yaml'
3
+
4
+ export interface FlowFrontmatter {
5
+ // raw `sootsim open` target: port, dev-server URL, bundle URL, or shell URL
6
+ app?: string | number
7
+ device?: string
8
+ // pin the sim color scheme for this flow run (screenshot baselines are
9
+ // theme-sensitive; a dark capture can never match a light baseline)
10
+ theme?: 'light' | 'dark'
11
+ electron?: boolean
12
+ // maestro-compatible: appId is accepted but not used by sootsim (ios bundle id)
13
+ appId?: string
14
+ // maestro-compatible: flow-level env vars, defined into the flow's JS
15
+ // context before the first step runs
16
+ env?: Record<string, string>
17
+ // maestro-compatible: workspace tag filtering (config.yml includeTags/excludeTags)
18
+ tags?: string[]
19
+ }
20
+
21
+ // parse flow file. accepts three shapes:
22
+ // 1. plain yaml array of steps (no frontmatter)
23
+ // 2. sootsim style: ---\n<fm>\n---\n<steps>
24
+ // 3. maestro style: <fm>\n---\n<steps> (multi-doc yaml, fm first)
25
+ // maestro compat matters because real users have real .maestro/*.yaml files
26
+ // and shouldn't have to rewrite them to drive sootsim.
27
+ //
28
+ // `${...}` templates are NOT resolved here — interpolation happens per-step
29
+ // at execution time in the flow runner's JS context (matching maestro), so
30
+ // values produced mid-flow (runScript output.*, copyTextFrom) resolve in
31
+ // later steps and a missing variable fails loudly at the exact step.
32
+ export function parseFlowFile(content: string): {
33
+ frontmatter: FlowFrontmatter
34
+ steps: any[]
35
+ } {
36
+ let frontmatter: FlowFrontmatter = {}
37
+ let body = content
38
+ const normalizeSteps = (steps: unknown): any[] => {
39
+ return Array.isArray(steps) ? steps : []
40
+ }
41
+
42
+ // shape 2: leading ---\n<fm>\n---\n<body>
43
+ const fmMatch = content.match(/^---\n([\s\S]*?)\n---\n?/)
44
+ if (fmMatch) {
45
+ try {
46
+ frontmatter = (yaml.parse(fmMatch[1]) as FlowFrontmatter) ?? {}
47
+ } catch (e) {
48
+ console.warn(` warn: could not parse frontmatter: ${(e as Error).message}`)
49
+ }
50
+ body = content.slice(fmMatch[0].length)
51
+ const steps = yaml.parse(body)
52
+ return { frontmatter, steps: normalizeSteps(steps) }
53
+ }
54
+
55
+ // shape 3: maestro multi-doc (fm-first, then `---`, then steps)
56
+ // detect cheaply: a `---` separator on its own line not at position 0
57
+ if (/\n---\s*\n/.test(content)) {
58
+ try {
59
+ const docs = yaml.parseAllDocuments(content)
60
+ if (docs.length >= 2) {
61
+ const fm = docs[0].toJS() as FlowFrontmatter | null
62
+ const st = docs[docs.length - 1].toJS()
63
+ return {
64
+ frontmatter: fm && typeof fm === 'object' ? fm : {},
65
+ steps: normalizeSteps(st),
66
+ }
67
+ }
68
+ } catch (e) {
69
+ console.warn(` warn: could not parse multi-doc flow: ${(e as Error).message}`)
70
+ }
71
+ }
72
+
73
+ // shape 1: plain steps array
74
+ const steps = yaml.parse(body)
75
+ return { frontmatter, steps: normalizeSteps(steps) }
76
+ }
77
+
78
+ const INTERACTION_KEYS = new Set([
79
+ 'tapOn',
80
+ 'inputText',
81
+ 'pressKey',
82
+ 'dispatchKey',
83
+ 'hideKeyboard',
84
+ 'swipe',
85
+ 'pinch',
86
+ 'scroll',
87
+ 'scrollUntilVisible',
88
+ ])
89
+
90
+ export function validateFlowSteps(steps: any[]): string[] {
91
+ const issues: string[] = []
92
+ if (!Array.isArray(steps) || steps.length === 0) {
93
+ issues.push('flow body must be a non-empty YAML array of steps')
94
+ return issues
95
+ }
96
+
97
+ let interactions = 0
98
+ for (const step of steps) {
99
+ if (step && typeof step === 'object') {
100
+ const keys = Object.keys(step)
101
+ if (keys.some((key) => INTERACTION_KEYS.has(key))) interactions += 1
102
+ } else if (typeof step === 'string' && INTERACTION_KEYS.has(step)) {
103
+ interactions += 1
104
+ }
105
+ }
106
+
107
+ if (interactions === 0) {
108
+ issues.push(
109
+ 'no interaction steps found (expected at least one tapOn / inputText / pressKey / swipe / scroll)',
110
+ )
111
+ }
112
+
113
+ return issues
114
+ }
115
+
116
+ // structural-only validator used by CI runners before uploading the flow
117
+ // artifact. we only check: file exists, parseable yaml, has at least one
118
+ // real interaction step. playback validation is a separate concern handled
119
+ // by `sootsim maestro end --validate` during the authoring session.
120
+ export function validateFlowFile(flowPath: string): string[] {
121
+ if (!fs.existsSync(flowPath)) {
122
+ return [`file not found: ${flowPath}`]
123
+ }
124
+
125
+ let content: string
126
+ try {
127
+ content = fs.readFileSync(flowPath, 'utf8')
128
+ } catch (e) {
129
+ return [`cannot read file: ${(e as Error).message}`]
130
+ }
131
+
132
+ if (content.trim().length === 0) {
133
+ return ['file is empty']
134
+ }
135
+
136
+ try {
137
+ const parsed = parseFlowFile(content)
138
+ return validateFlowSteps(parsed.steps)
139
+ } catch (e) {
140
+ return [`yaml parse error: ${(e as Error).message}`]
141
+ }
142
+ }
@@ -0,0 +1,120 @@
1
+ // flow-live-status — pushes the flow runner's live progress into the sim it
2
+ // is driving, over the same ws bridge the steps execute on. the engine's
3
+ // flow-run-store renders it in the shell devtools "test" tab and replies
4
+ // with the pause intent, which the runner honors between steps.
5
+ //
6
+ // pushes are best-effort: an engine without the `flowStatus` handler (an
7
+ // older shell) throws "unknown command type" once and the reporter disables
8
+ // itself for the rest of the run. live status must never fail a test.
9
+
10
+ import type { WsBridge } from './ws-bridge'
11
+
12
+ export type LiveStepStatus = 'running' | 'success' | 'failure' | 'skipped'
13
+
14
+ type PlanStep = { index: number; name: string; target?: string }
15
+
16
+ const PAUSE_POLL_MS = 400
17
+
18
+ function replyPaused(reply: unknown) {
19
+ if (typeof reply !== 'object' || reply == null || !('control' in reply)) return false
20
+ const control = reply.control
21
+ return (
22
+ typeof control === 'object' &&
23
+ control != null &&
24
+ 'paused' in control &&
25
+ control.paused === true
26
+ )
27
+ }
28
+
29
+ export class FlowLiveStatusReporter {
30
+ private disabled = false
31
+ private paused = false
32
+ private lastSimId: string | undefined
33
+ private plannedSteps: PlanStep[] | null = null
34
+ private readonly completedSteps = new Map<
35
+ number,
36
+ { index: number; status: LiveStepStatus; durationMs?: number; error?: string }
37
+ >()
38
+ private readonly runId = `flr_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`
39
+
40
+ constructor(
41
+ private bridge: WsBridge,
42
+ private flowName: string | null,
43
+ private getSimId?: () => string | undefined,
44
+ ) {}
45
+
46
+ private async send(payload: Record<string, unknown>, simId: string | undefined) {
47
+ const reply: unknown = await this.bridge.send({
48
+ type: 'flowStatus',
49
+ simId,
50
+ status: { runId: this.runId, flowName: this.flowName, ...payload },
51
+ })
52
+ this.paused = replyPaused(reply)
53
+ }
54
+
55
+ private async push(payload: Record<string, unknown>): Promise<void> {
56
+ if (this.disabled) return
57
+ try {
58
+ const simId = this.getSimId?.()
59
+ if (
60
+ simId !== this.lastSimId &&
61
+ this.lastSimId != null &&
62
+ this.plannedSteps &&
63
+ payload.phase !== 'plan'
64
+ ) {
65
+ await this.send(
66
+ { phase: 'plan', state: 'running', steps: this.plannedSteps },
67
+ simId,
68
+ )
69
+ for (const step of this.completedSteps.values()) {
70
+ await this.send({ phase: 'step', state: 'running', step }, simId)
71
+ }
72
+ }
73
+ this.lastSimId = simId
74
+ await this.send(payload, simId)
75
+ } catch (error) {
76
+ this.disabled = true
77
+ console.log(
78
+ `[flow] live status unavailable (${error instanceof Error ? error.message.slice(0, 120) : error}) — continuing without the devtools rail`,
79
+ )
80
+ }
81
+ }
82
+
83
+ async plan(steps: PlanStep[]): Promise<void> {
84
+ this.plannedSteps = steps.map((step) => ({ ...step }))
85
+ this.completedSteps.clear()
86
+ await this.push({ phase: 'plan', state: 'running', steps })
87
+ }
88
+
89
+ async step(
90
+ index: number,
91
+ status: LiveStepStatus,
92
+ extra?: { durationMs?: number; error?: string },
93
+ ): Promise<void> {
94
+ const step = { index, status, ...extra }
95
+ await this.push({
96
+ phase: 'step',
97
+ state: 'running',
98
+ step,
99
+ })
100
+ this.completedSteps.set(index, step)
101
+ }
102
+
103
+ async end(state: 'passed' | 'failed'): Promise<void> {
104
+ await this.push({ phase: 'end', state })
105
+ }
106
+
107
+ // park while the devtools pause toggle is on. checked between top-level
108
+ // steps only, so a pause never interrupts a step mid-gesture.
109
+ async waitWhilePaused(): Promise<void> {
110
+ if (this.disabled) return
111
+ while (this.paused) {
112
+ console.log('[flow] paused from devtools — waiting…')
113
+ while (this.paused && !this.disabled) {
114
+ await new Promise((resolve) => setTimeout(resolve, PAUSE_POLL_MS))
115
+ await this.push({ phase: 'heartbeat', state: 'paused' })
116
+ }
117
+ if (!this.disabled) console.log('[flow] resumed')
118
+ }
119
+ }
120
+ }
@@ -0,0 +1,187 @@
1
+ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'fs'
2
+ import { tmpdir } from 'os'
3
+ import { dirname, join, resolve } from 'path'
4
+ import yaml from 'yaml'
5
+ import { validateFlowSteps } from './flow-file'
6
+
7
+ export type FlowDraftStep = Record<string, unknown>
8
+
9
+ export interface FlowCandidate {
10
+ step: FlowDraftStep
11
+ summary: string
12
+ source: string
13
+ recordedAt: string
14
+ }
15
+
16
+ export interface FlowSessionState {
17
+ version: 1
18
+ startedAt: string
19
+ updatedAt: string
20
+ steps: FlowDraftStep[]
21
+ candidate: FlowCandidate | null
22
+ }
23
+
24
+ const FLOW_SESSION_VERSION = 1 as const
25
+
26
+ export function getFlowSessionPath() {
27
+ return resolve(
28
+ process.env.SOOTSIM_FLOW_SESSION_PATH || join(tmpdir(), 'sootsim-flow-session.json'),
29
+ )
30
+ }
31
+
32
+ function writeFlowSession(state: FlowSessionState) {
33
+ const filepath = getFlowSessionPath()
34
+ mkdirSync(dirname(filepath), { recursive: true })
35
+ writeFileSync(filepath, JSON.stringify(state, null, 2) + '\n')
36
+ }
37
+
38
+ export function readFlowSession(): FlowSessionState | null {
39
+ const filepath = getFlowSessionPath()
40
+ if (!existsSync(filepath)) return null
41
+
42
+ try {
43
+ const parsed = JSON.parse(readFileSync(filepath, 'utf8')) as Partial<FlowSessionState>
44
+ if (
45
+ parsed.version !== FLOW_SESSION_VERSION ||
46
+ !Array.isArray(parsed.steps) ||
47
+ typeof parsed.startedAt !== 'string' ||
48
+ typeof parsed.updatedAt !== 'string'
49
+ ) {
50
+ rmSync(filepath, { force: true })
51
+ return null
52
+ }
53
+
54
+ return {
55
+ version: FLOW_SESSION_VERSION,
56
+ startedAt: parsed.startedAt,
57
+ updatedAt: parsed.updatedAt,
58
+ steps: parsed.steps as FlowDraftStep[],
59
+ candidate: parsed.candidate ?? null,
60
+ }
61
+ } catch {
62
+ rmSync(filepath, { force: true })
63
+ return null
64
+ }
65
+ }
66
+
67
+ export function hasActiveFlowSession() {
68
+ return readFlowSession() !== null
69
+ }
70
+
71
+ export function startFlowSession() {
72
+ const now = new Date().toISOString()
73
+ const state: FlowSessionState = {
74
+ version: FLOW_SESSION_VERSION,
75
+ startedAt: now,
76
+ updatedAt: now,
77
+ steps: [],
78
+ candidate: null,
79
+ }
80
+ writeFlowSession(state)
81
+ return { path: getFlowSessionPath(), state }
82
+ }
83
+
84
+ export function clearFlowSession() {
85
+ rmSync(getFlowSessionPath(), { force: true })
86
+ }
87
+
88
+ export function finalizeFlowSession() {
89
+ clearFlowSession()
90
+ }
91
+
92
+ export function rememberFlowCandidate(input: {
93
+ step: FlowDraftStep
94
+ summary: string
95
+ source: string
96
+ }) {
97
+ const state = readFlowSession()
98
+ if (!state) return { active: false as const }
99
+
100
+ // a pending candidate that was never `maestro keep`'d is silently dropped
101
+ // when a new action arrives — that's the "skip mistakes" model. surface
102
+ // the replaced action so the caller can warn (a kept action is never
103
+ // lost; an unkept one is).
104
+ const replaced = state.candidate
105
+ const next: FlowSessionState = {
106
+ ...state,
107
+ updatedAt: new Date().toISOString(),
108
+ candidate: {
109
+ step: input.step,
110
+ summary: input.summary,
111
+ source: input.source,
112
+ recordedAt: new Date().toISOString(),
113
+ },
114
+ }
115
+ writeFlowSession(next)
116
+ return { active: true as const, candidate: next.candidate, replaced }
117
+ }
118
+
119
+ export function keepFlowCandidate() {
120
+ const state = readFlowSession()
121
+ if (!state)
122
+ return { active: false as const, kept: false as const, reason: 'no-session' }
123
+ if (!state.candidate) {
124
+ return { active: true as const, kept: false as const, reason: 'no-candidate' }
125
+ }
126
+
127
+ const keptCandidate = state.candidate
128
+ const next: FlowSessionState = {
129
+ ...state,
130
+ updatedAt: new Date().toISOString(),
131
+ steps: [...state.steps, keptCandidate.step],
132
+ candidate: null,
133
+ }
134
+ writeFlowSession(next)
135
+
136
+ return {
137
+ active: true as const,
138
+ kept: true as const,
139
+ candidate: keptCandidate,
140
+ stepCount: next.steps.length,
141
+ }
142
+ }
143
+
144
+ export function formatFlowDraftYaml(steps: FlowDraftStep[]) {
145
+ return yaml.stringify(steps).trimEnd() + '\n'
146
+ }
147
+
148
+ export function exportFlowSession(outputPath?: string) {
149
+ const state = readFlowSession()
150
+ if (!state) return { active: false as const }
151
+
152
+ const yamlText = formatFlowDraftYaml(state.steps)
153
+ const issues = validateFlowSteps(state.steps)
154
+ if (issues.length > 0) {
155
+ return {
156
+ active: true as const,
157
+ valid: false as const,
158
+ issues,
159
+ yaml: yamlText,
160
+ stepCount: state.steps.length,
161
+ outputPath: null,
162
+ }
163
+ }
164
+
165
+ let resolvedOutputPath: string | null = null
166
+ if (outputPath) {
167
+ resolvedOutputPath = resolve(outputPath)
168
+ mkdirSync(dirname(resolvedOutputPath), { recursive: true })
169
+ writeFileSync(resolvedOutputPath, yamlText)
170
+ }
171
+
172
+ return {
173
+ active: true as const,
174
+ valid: true as const,
175
+ issues: [],
176
+ yaml: yamlText,
177
+ stepCount: state.steps.length,
178
+ outputPath: resolvedOutputPath,
179
+ }
180
+ }
181
+
182
+ export function endFlowSession(outputPath?: string) {
183
+ const result = exportFlowSession(outputPath)
184
+ if (!result.active) return result
185
+ finalizeFlowSession()
186
+ return result
187
+ }
package/cli/help.ts ADDED
@@ -0,0 +1,80 @@
1
+ import {
2
+ renderCliCommandHelp,
3
+ renderCliGroupHelp,
4
+ renderCliIndexHelp,
5
+ } from '@sootsim/skills/cli/renderers'
6
+ import { formatFlagHelp } from 'sootsim-engine/settings/cli-flags'
7
+ import { BETA_TAGLINE, IS_BETA } from '../src/beta'
8
+ import {
9
+ DEFAULT_SOOTSIM_BRIDGE_PORT,
10
+ DEFAULT_SOOTSIM_BRIDGE_URL,
11
+ } from '../src/bridge-constants'
12
+ import { getCliVersion } from '../src/cli-version'
13
+ import { rnxPublicBrand } from '../src/public-brand'
14
+ import { maybeHint } from './hints'
15
+ import { getRuntimeSummary } from './runtime-summary'
16
+
17
+ // header line shown at the top of `--help` so users can always tell what
18
+ // they're running — the CLI npm version and the active engine runtime
19
+ // version are independent, which is exactly the confusion `sootsim
20
+ // upgrade` exists to clear up.
21
+ function versionSummaryLine(): string {
22
+ return `${rnxPublicBrand.commandName} CLI v${getCliVersion()} · ${getRuntimeSummary().primary}`
23
+ }
24
+
25
+ export function printHelp() {
26
+ const base = renderCliIndexHelp(
27
+ {
28
+ bridgePort: DEFAULT_SOOTSIM_BRIDGE_PORT,
29
+ defaultShellUrl: DEFAULT_SOOTSIM_BRIDGE_URL,
30
+ },
31
+ formatFlagHelp(),
32
+ )
33
+ const betaLine = IS_BETA ? `\n${BETA_TAGLINE}\n` : ''
34
+ console.log(
35
+ `${versionSummaryLine()}\n${betaLine}${base}
36
+ `,
37
+ )
38
+ // once-ever nudge so users learn the clean teardown path before they need it.
39
+ // the npm preuninstall hook runs the same teardown automatically when it
40
+ // fires; this is the documented manual fallback.
41
+ maybeHint('clean-uninstall')
42
+ }
43
+
44
+ // bare `sootsim` prints the registry-backed surface. setup/open own work.
45
+
46
+ // print the help page for a grouping verb (do / get / debug / wait) built
47
+ // entirely from the registry — no hardcoded strings. returns true if the
48
+ // group was known, false if the caller should fall through.
49
+ export function printGroupHelp(group: string): boolean {
50
+ const output = renderCliGroupHelp(
51
+ group,
52
+ {
53
+ bridgePort: DEFAULT_SOOTSIM_BRIDGE_PORT,
54
+ defaultShellUrl: DEFAULT_SOOTSIM_BRIDGE_URL,
55
+ },
56
+ formatFlagHelp(),
57
+ )
58
+ if (!output) return false
59
+ console.log(`${output}\n`)
60
+ return true
61
+ }
62
+
63
+ export function printCommandHelp(
64
+ name: string,
65
+ options: { prefer?: 'command' | 'verb'; group?: string | null } = {},
66
+ ) {
67
+ const output = renderCliCommandHelp(
68
+ name,
69
+ {
70
+ bridgePort: DEFAULT_SOOTSIM_BRIDGE_PORT,
71
+ defaultShellUrl: DEFAULT_SOOTSIM_BRIDGE_URL,
72
+ },
73
+ options,
74
+ )
75
+ if (!output) {
76
+ console.log(`unknown command: ${name}`)
77
+ return
78
+ }
79
+ console.log(`${output}\n`)
80
+ }