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,353 @@
1
+ // shared helpers for inspect subcommands.
2
+ //
3
+ // pulled out of the monolithic inspect.ts so each subcommand can live in its
4
+ // own file without reaching back into a 4000-line switch. everything here is
5
+ // call-agnostic: just bridge-facing helpers that any subcommand can use.
6
+
7
+ import {
8
+ callInBridge,
9
+ callInBridgeWrite,
10
+ createBridge,
11
+ type ParsedBridgeCliArgs,
12
+ type WsBridge,
13
+ } from '../../ws-bridge'
14
+ import {
15
+ getShellState,
16
+ isShellCommandUnavailable,
17
+ readyProbeHasContent,
18
+ READY_NODE_STABLE_MS,
19
+ WAIT_READY_PROBE,
20
+ type ReadyProbe,
21
+ } from './core'
22
+ import type { SootSimScreenTransitionWaitResult } from '@sootsim/globals'
23
+
24
+ // shell-state helpers live in the transport-agnostic kernel module so the CLI
25
+ // and the in-browser agent share one implementation. re-exported here so
26
+ // existing subcommand imports (`./shared`) keep working.
27
+ export { getShellState, isShellCommandUnavailable }
28
+
29
+ export const SCREEN_TRANSITION_PREFLIGHT = {
30
+ timeoutMs: 1800,
31
+ settleMs: 48,
32
+ startWindowMs: 64,
33
+ } as const
34
+
35
+ export function sleep(ms: number) {
36
+ return new Promise((resolve) => setTimeout(resolve, ms))
37
+ }
38
+
39
+ // true when the caller passed --json. read verbs use this to route their
40
+ // payload to stdout-as-json and send everything else (headers, hints,
41
+ // warnings) to stderr so `jq` on stdout never sees framing noise.
42
+ export function wantsJson(args: string[]): boolean {
43
+ return args.includes('--json')
44
+ }
45
+
46
+ // emit a stable JSON document on stdout. no trailing prose, no ANSI,
47
+ // no colors. the 2-space indent matches the rest of the CLI and is
48
+ // cheap enough that pretty-printing by default is fine for bisect
49
+ // scripts — consumers who care pipe through `jq -c`.
50
+ export function printJson(payload: unknown): void {
51
+ process.stdout.write(`${JSON.stringify(payload ?? null, null, 2)}\n`)
52
+ }
53
+
54
+ // warnings, hints, "screen transition still active" messages — anything
55
+ // that isn't the payload. always goes to stderr so stdout stays parseable.
56
+ export function printWarn(msg: string): void {
57
+ process.stderr.write(`${msg}\n`)
58
+ }
59
+
60
+ // 24-hour wall-clock + relative offset for log/error/network timestamps.
61
+ // agents reading these care most about "is this new or stale" — append
62
+ // "+<n>s" for things younger than 60s. older entries keep the relative
63
+ // stamp in m/h/d so it's still scannable without diffing wall-clock times.
64
+ //
65
+ // returns "HH:MM:SS +Xs" where wall clock is locale-independent 24-hour.
66
+ // callers can wrap it in their own padding/dim ANSI.
67
+ export function formatLogTimestamp(ts: number, nowMs: number = Date.now()): string {
68
+ const d = new Date(ts)
69
+ const wall = `${pad2(d.getHours())}:${pad2(d.getMinutes())}:${pad2(d.getSeconds())}`
70
+ const deltaMs = nowMs - ts
71
+ if (deltaMs < 0) return wall
72
+ const sec = Math.round(deltaMs / 1000)
73
+ if (sec < 60) return `${wall} +${sec}s`
74
+ if (sec < 3600) return `${wall} +${Math.round(sec / 60)}m`
75
+ if (sec < 86400) return `${wall} +${Math.round(sec / 3600)}h`
76
+ return `${wall} +${Math.round(sec / 86400)}d`
77
+ }
78
+
79
+ function pad2(n: number): string {
80
+ return n < 10 ? `0${n}` : String(n)
81
+ }
82
+
83
+ export async function callTestBridge<T = unknown>(
84
+ bridge: WsBridge,
85
+ method: string,
86
+ ...args: unknown[]
87
+ ): Promise<T> {
88
+ return callInBridge<T>(bridge, `__sootsimTest.${method}`, ...args)
89
+ }
90
+
91
+ export async function callShellCommand<T = unknown>(
92
+ bridge: WsBridge,
93
+ method: 'launchApp' | 'goHome' | 'openSwitcher' | 'openSwitcherCard',
94
+ ...args: unknown[]
95
+ ): Promise<T> {
96
+ return callInBridgeWrite<T>(bridge, `SootSim.bridges.mainShell.${method}`, ...args)
97
+ }
98
+
99
+ export async function maybeWaitForStartedScreenTransitions(
100
+ bridge: WsBridge,
101
+ opts: { verbose?: boolean } = {},
102
+ ) {
103
+ try {
104
+ const result = await callTestBridge<SootSimScreenTransitionWaitResult>(
105
+ bridge,
106
+ 'waitForScreenTransitions',
107
+ SCREEN_TRANSITION_PREFLIGHT,
108
+ )
109
+ if (!opts.verbose || !result?.started) return
110
+ if (result.timedOut) {
111
+ console.log(
112
+ ` screen transition still active after ${result.waitedMs}ms; continuing`,
113
+ )
114
+ return
115
+ }
116
+ if (result.waitedMs > 0) {
117
+ console.log(` waited ${result.waitedMs}ms for screen transition settle`)
118
+ }
119
+ } catch {
120
+ // best-effort preflight only — older runtimes or boot races should not
121
+ // block the actual command.
122
+ }
123
+ }
124
+
125
+ export async function callShellCommandWhenReady<T = unknown>(
126
+ bridge: WsBridge,
127
+ method: 'launchApp' | 'goHome' | 'openSwitcher' | 'openSwitcherCard',
128
+ readyTimeoutMs: number,
129
+ ...args: unknown[]
130
+ ): Promise<T> {
131
+ const deadline = Date.now() + Math.max(0, readyTimeoutMs)
132
+ while (true) {
133
+ try {
134
+ return await callShellCommand<T>(bridge, method, ...args)
135
+ } catch (error) {
136
+ if (!isShellCommandUnavailable(error) || Date.now() >= deadline) throw error
137
+ await sleep(50)
138
+ }
139
+ }
140
+ }
141
+
142
+ export async function waitForSimReady(
143
+ wsPort: number,
144
+ commandTimeoutMs: number,
145
+ simId?: string,
146
+ opts: {
147
+ attempts?: number
148
+ intervalMs?: number
149
+ minNodeCount?: number
150
+ simIdSource?: ParsedBridgeCliArgs['simIdSource']
151
+ } = {},
152
+ ) {
153
+ const attempts = opts.attempts ?? 30
154
+ const intervalMs = opts.intervalMs ?? 500
155
+ const minNodeCount = opts.minNodeCount ?? 10
156
+
157
+ for (let i = 0; i < attempts; i++) {
158
+ const bridge = createBridge(wsPort, {
159
+ commandTimeoutMs,
160
+ simId,
161
+ simIdSource: opts.simIdSource,
162
+ })
163
+ try {
164
+ const count = await bridge.send({
165
+ type: 'evaluate',
166
+ code: '(async () => (await window.__sootsimTest?.getNodeCount()) || 0)()',
167
+ })
168
+ if (typeof count === 'number' && count > minNodeCount) {
169
+ return { bridge, count }
170
+ }
171
+ } catch {
172
+ // still loading or disconnected
173
+ }
174
+ bridge.close()
175
+ await sleep(intervalMs)
176
+ }
177
+
178
+ return null
179
+ }
180
+
181
+ // waits until a bridge can evaluate a trivial expression. used by the reload
182
+ // full-page path after window.location.reload() to re-establish a bridge
183
+ // before handing off to pollForReloadReady. intentionally does not gate on
184
+ // node count — that check belongs in the subsequent ready poll, which uses
185
+ // the __sootsimExternalAppReady flag as its primary signal.
186
+ export async function waitForBridgeConnected(
187
+ wsPort: number,
188
+ commandTimeoutMs: number,
189
+ simId: string | undefined,
190
+ opts: {
191
+ timeoutMs?: number
192
+ intervalMs?: number
193
+ simIdSource?: ParsedBridgeCliArgs['simIdSource']
194
+ } = {},
195
+ ): Promise<WsBridge | null> {
196
+ const timeoutMs = opts.timeoutMs ?? 8000
197
+ const intervalMs = opts.intervalMs ?? 250
198
+ const deadline = Date.now() + timeoutMs
199
+ while (Date.now() < deadline) {
200
+ const bridge = createBridge(wsPort, {
201
+ commandTimeoutMs,
202
+ simId,
203
+ simIdSource: opts.simIdSource,
204
+ })
205
+ try {
206
+ await bridge.send({ type: 'evaluate', code: '1' })
207
+ return bridge
208
+ } catch {
209
+ bridge.close()
210
+ }
211
+ await sleep(intervalMs)
212
+ }
213
+ return null
214
+ }
215
+
216
+ export interface ReloadReadyResult {
217
+ ready: boolean
218
+ source: 'flag' | 'nodes-fallback' | 'error-bail' | 'timeout'
219
+ elapsedMs: number
220
+ nodes: number
221
+ targets: number
222
+ liveFrameActive: boolean
223
+ liveFrameChannels: number
224
+ liveFramePublishes: number
225
+ flag: unknown
226
+ loadingText: string
227
+ externalReady: boolean | null
228
+ externalStatus: string
229
+ externalError: string
230
+ errors: number
231
+ }
232
+
233
+ // drives the post-reload wait with short bridge-side probes. used by
234
+ // `sootsim reload` (both the in-place hot-remount and the full-page reload
235
+ // paths). primary ready signal is window.__sootsimExternalAppReady, set by
236
+ // shell-events-bridge on the `sootsim:externalAppReady` event, plus visible
237
+ // inspectable content (or a larger non-placeholder tree) and a brief stable
238
+ // node count. a single long in-page evaluate can hold a stale shell-owned tree
239
+ // snapshot until it returns; separate probes let the host process pending
240
+ // shellTreeUpdate traffic between reads. bails early if the console picks up
241
+ // errors after a grace period with the flag still unset — fatal bundle/eval
242
+ // failures pile up errors fast and never emit ready, so waiting the full
243
+ // budget just wastes wall time. falls back to the same content gate on timeout
244
+ // so apps that never wire up the shell-events bridge don't hard-fail reload.
245
+ export async function pollForReloadReady(
246
+ bridge: WsBridge,
247
+ opts: {
248
+ timeoutMs?: number
249
+ errorGraceMs?: number
250
+ pollIntervalMs?: number
251
+ } = {},
252
+ ): Promise<ReloadReadyResult> {
253
+ const timeoutMs = opts.timeoutMs ?? 10000
254
+ const errorGraceMs = opts.errorGraceMs ?? 3000
255
+ const pollIntervalMs = opts.pollIntervalMs ?? 200
256
+ const start = Date.now()
257
+ const deadline = start + timeoutMs
258
+ let lastNodes = -1
259
+ let nodeStableSince = start
260
+ let lastExternalReady: unknown = null
261
+ let lastExternalErrored = false
262
+ let last: ReloadReadyResult = {
263
+ ready: false,
264
+ source: 'timeout',
265
+ elapsedMs: 0,
266
+ nodes: 0,
267
+ targets: 0,
268
+ liveFrameActive: false,
269
+ liveFrameChannels: 0,
270
+ liveFramePublishes: 0,
271
+ flag: undefined,
272
+ loadingText: '',
273
+ externalReady: null,
274
+ externalStatus: '',
275
+ externalError: '',
276
+ errors: 0,
277
+ }
278
+
279
+ while (Date.now() < deadline) {
280
+ try {
281
+ const probe = (await bridge.send({
282
+ type: 'evaluate',
283
+ code: WAIT_READY_PROBE,
284
+ })) as Partial<ReadyProbe> | null
285
+ const elapsedMs = Date.now() - start
286
+ const nodes = Number(probe?.nodes) || 0
287
+ const targets = Number(probe?.targets) || 0
288
+ const liveFrameActive = probe?.liveFrameActive === true
289
+ const liveFrameChannels = Number(probe?.liveFrameChannels) || 0
290
+ const liveFramePublishes = Number(probe?.liveFramePublishes) || 0
291
+ const errors = Number(probe?.errors) || 0
292
+ const readyFlag = probe?.flag === true
293
+ const externalReady = probe?.externalReady
294
+ const loadingText =
295
+ typeof probe?.loadingText === 'string' ? probe.loadingText.trim() : ''
296
+ const externalStatus =
297
+ typeof probe?.externalStatus === 'string' ? probe.externalStatus.trim() : ''
298
+ const externalError =
299
+ typeof probe?.externalError === 'string' ? probe.externalError.trim() : ''
300
+ const externalErrored =
301
+ typeof probe?.externalError === 'string' && externalError.length > 0
302
+ lastExternalReady = externalReady
303
+ lastExternalErrored = externalErrored
304
+ if (nodes !== lastNodes) {
305
+ lastNodes = nodes
306
+ nodeStableSince = elapsedMs
307
+ }
308
+ last = {
309
+ ready: false,
310
+ source: 'timeout',
311
+ elapsedMs,
312
+ nodes,
313
+ targets,
314
+ liveFrameActive,
315
+ liveFrameChannels,
316
+ liveFramePublishes,
317
+ flag: probe?.flag,
318
+ loadingText,
319
+ externalReady: typeof externalReady === 'boolean' ? externalReady : null,
320
+ externalStatus,
321
+ externalError,
322
+ errors,
323
+ }
324
+ if (
325
+ readyFlag &&
326
+ externalReady !== false &&
327
+ !externalErrored &&
328
+ !loadingText &&
329
+ readyProbeHasContent({ liveFrameActive, nodes, targets })
330
+ ) {
331
+ if (elapsedMs - nodeStableSince >= READY_NODE_STABLE_MS) {
332
+ return { ...last, ready: true, source: 'flag' }
333
+ }
334
+ }
335
+ if (elapsedMs >= errorGraceMs && errors > 0 && !readyFlag) {
336
+ return { ...last, ready: false, source: 'error-bail' }
337
+ }
338
+ } catch {
339
+ // transient bridge disconnect during full-page reload — keep polling.
340
+ }
341
+ await sleep(pollIntervalMs)
342
+ }
343
+
344
+ if (
345
+ lastExternalReady !== false &&
346
+ !lastExternalErrored &&
347
+ !last.loadingText &&
348
+ readyProbeHasContent(last)
349
+ ) {
350
+ return { ...last, ready: true, source: 'nodes-fallback' }
351
+ }
352
+ return { ...last, ready: false, source: 'timeout', elapsedMs: Date.now() - start }
353
+ }
@@ -0,0 +1,14 @@
1
+ import { sleep } from './shared'
2
+
3
+ export async function runSleepSubcommand(opts: {
4
+ positional: string[]
5
+ inspectUsage: (name: string, tail: string) => string
6
+ }): Promise<void> {
7
+ const seconds = opts.positional[1] ? Number(opts.positional[1]) : 0.5
8
+ if (!Number.isFinite(seconds) || seconds < 0) {
9
+ console.error(opts.inspectUsage('sleep', '[seconds]'))
10
+ process.exit(1)
11
+ }
12
+ await sleep(seconds * 1000)
13
+ console.log(` slept ${seconds}s`)
14
+ }
@@ -0,0 +1,32 @@
1
+ // `sootsim get tree` — raw node tree dump, default depth 5.
2
+ //
3
+ // goes through the merged test bridge (shell + tenant) rather than the raw
4
+ // tree message, which only walks the tenant root.
5
+
6
+ import { inspectTree } from './core'
7
+ import { printJson, wantsJson } from './shared'
8
+ import type { WsBridge } from '../../ws-bridge'
9
+
10
+ export async function runTreeSubcommand(opts: {
11
+ bridge: WsBridge
12
+ args: string[]
13
+ positional: string[]
14
+ }): Promise<void> {
15
+ const { bridge, args, positional } = opts
16
+ // --boxes: structured nested rects ({ type, testID?, text?, x, y, w, h,
17
+ // bg?, fg?, br?, children? }) in absolute device coords — for layout
18
+ // tooling that needs real geometry, not the rendered text dump. deeper
19
+ // default depth because geometry consumers want the whole tree.
20
+ const boxes = args.includes('--boxes')
21
+ const depth = positional[1] ? Number(positional[1]) : boxes ? 50 : 5
22
+ const { tree } = await inspectTree(
23
+ bridge,
24
+ depth,
25
+ boxes ? { format: 'boxes' } : undefined,
26
+ )
27
+ if (wantsJson(args)) {
28
+ printJson({ depth, tree: tree ?? null })
29
+ return
30
+ }
31
+ console.log(typeof tree === 'string' ? tree : JSON.stringify(tree, null, 2))
32
+ }
@@ -0,0 +1,17 @@
1
+ // `sootsim get url` — prints the current page URL the bridge is attached to.
2
+
3
+ import { inspectUrl } from './core'
4
+ import { printJson, wantsJson } from './shared'
5
+ import type { WsBridge } from '../../ws-bridge'
6
+
7
+ export async function runUrlSubcommand(
8
+ bridge: WsBridge,
9
+ opts: { args: string[] } = { args: [] },
10
+ ): Promise<void> {
11
+ const result = await inspectUrl(bridge)
12
+ if (wantsJson(opts.args)) {
13
+ printJson(result)
14
+ return
15
+ }
16
+ console.log(result.url)
17
+ }
@@ -0,0 +1,210 @@
1
+ // sootsim wait event — block until a semantic timeline event of the
2
+ // given kind appears, or timeout.
3
+ //
4
+ // usage:
5
+ // sootsim wait event <kind> [--max-ms 5000] [--since now|cursor]
6
+ // sootsim wait event fetch [--filter <substring>]
7
+ // sootsim wait event screen [--equals SignInPage]
8
+ //
9
+ // the existing wait family is layout-driven: `wait selector` polls
10
+ // for a testID, `wait idle` polls for rAF + animation settling,
11
+ // `wait ready` polls for bundle paint. `wait event` is the
12
+ // timeline-driven companion: agents that want to drive "tap → wait
13
+ // for the BeginSignIn fetch to land → tap next" no longer need to
14
+ // chain `do tap-text Continue` + `sleep 4` + `what-happened --kinds
15
+ // fetch --since 4s | grep BeginSignIn`. one read returns when the
16
+ // next event of the requested kind appears.
17
+ //
18
+ // the implementation polls timeline.recent() — server-side
19
+ // subscription would be cheaper but a 200ms poll is plenty for the
20
+ // human-paced UI events we care about and stays compatible with the
21
+ // existing query surface.
22
+
23
+ import type { WsBridge } from '../../ws-bridge'
24
+ import type {
25
+ SootSimTimelineEvent,
26
+ SootSimTimelineQuery,
27
+ SootSimTimelineQueryResult,
28
+ } from '@sootsim/globals'
29
+
30
+ interface WaitEventOptions {
31
+ bridge: WsBridge
32
+ args: string[]
33
+ positional: string[]
34
+ inspectUsage: (name: string, tail: string) => string
35
+ }
36
+
37
+ // every timeline event kind the cli currently knows about. matching
38
+ // the type on `SootSimTimelineEvent['kind']` would be more strict
39
+ // but the timeline can grow new kinds and we don't want this
40
+ // command to refuse an unknown one — the worst case is the wait
41
+ // just times out, which is exactly the right behaviour.
42
+ const KNOWN_KINDS = new Set([
43
+ 'app-launch',
44
+ 'toast',
45
+ 'keyboard',
46
+ 'screen',
47
+ 'flow-step',
48
+ 'route',
49
+ 'alert',
50
+ 'actionsheet',
51
+ 'picker',
52
+ 'notification',
53
+ 'fetch',
54
+ 'console',
55
+ 'shell',
56
+ 'scroll',
57
+ 'gesture',
58
+ 'text-input',
59
+ 'react-commit',
60
+ 'animation',
61
+ 'reanimated',
62
+ ])
63
+
64
+ function findFlagValue(args: string[], flag: string): string | undefined {
65
+ const idx = args.indexOf(flag)
66
+ if (idx >= 0 && idx + 1 < args.length) return args[idx + 1]
67
+ return undefined
68
+ }
69
+
70
+ function eventMatches(
71
+ ev: SootSimTimelineEvent,
72
+ opts: { filter?: string; equals?: string },
73
+ ): boolean {
74
+ if (!opts.filter && !opts.equals) return true
75
+ const data = ev.data as Record<string, unknown> | null
76
+ // build a one-string haystack from the most-likely-relevant fields
77
+ // per kind. agents asking for `wait event fetch --filter BeginSignIn`
78
+ // expect to match the URL; `wait event screen --equals Inbox`
79
+ // expects to match the screen name.
80
+ const haystack: string[] = []
81
+ if (data && typeof data === 'object') {
82
+ for (const key of [
83
+ 'url',
84
+ 'displayUrl',
85
+ 'message',
86
+ 'name',
87
+ 'activeName',
88
+ 'path',
89
+ 'pathname',
90
+ 'title',
91
+ 'phase',
92
+ 'event',
93
+ 'type',
94
+ 'kind',
95
+ ]) {
96
+ const v = data[key]
97
+ if (typeof v === 'string' && v.length > 0) haystack.push(v)
98
+ }
99
+ }
100
+ const joined = haystack.join(' ')
101
+ if (opts.equals) {
102
+ return haystack.some((s) => s === opts.equals)
103
+ }
104
+ if (opts.filter) {
105
+ return joined.toLowerCase().includes(opts.filter.toLowerCase())
106
+ }
107
+ return true
108
+ }
109
+
110
+ export async function runWaitEventSubcommand(opts: WaitEventOptions): Promise<void> {
111
+ const { bridge, args, positional, inspectUsage } = opts
112
+ // positional shape after verb-prefix strip and stripBooleanFlags:
113
+ // sootsim wait event <kind> → ['event', '<kind>']
114
+ // so positional[1] is the kind.
115
+ const kind = positional[1]
116
+ if (!kind) {
117
+ console.error(
118
+ inspectUsage(
119
+ 'wait event',
120
+ '<kind> [--max-ms 5000] [--filter <substring>] [--equals <exact>] [--since now|cursor]',
121
+ ),
122
+ )
123
+ process.exit(1)
124
+ }
125
+ if (!KNOWN_KINDS.has(kind)) {
126
+ console.error(
127
+ ` warning: '${kind}' is not a known timeline kind — waiting anyway. known: ${[...KNOWN_KINDS].sort().join(', ')}`,
128
+ )
129
+ }
130
+
131
+ const maxMsRaw = findFlagValue(args, '--max-ms')
132
+ const maxMs =
133
+ maxMsRaw && Number.isFinite(Number(maxMsRaw)) ? Math.max(100, Number(maxMsRaw)) : 5000
134
+
135
+ const filter = findFlagValue(args, '--filter')
136
+ const equals = findFlagValue(args, '--equals')
137
+ const sinceMode = findFlagValue(args, '--since') ?? 'now'
138
+ const jsonOut = args.includes('--json')
139
+
140
+ // capture the start watermark so we don't match an event that
141
+ // happened before this command. agents calling `wait event fetch`
142
+ // after a tap mean "the *next* fetch", not "any fetch since the
143
+ // session started". `--since cursor` falls back to the
144
+ // what-happened cursor semantics for callers that explicitly want
145
+ // that.
146
+ const startedAt = Date.now()
147
+ const deadline = startedAt + maxMs
148
+ const pollMs = 200
149
+
150
+ let lastWatermark = startedAt
151
+
152
+ while (Date.now() < deadline) {
153
+ const query: SootSimTimelineQuery = {
154
+ kinds: [kind] as SootSimTimelineQuery['kinds'],
155
+ since: sinceMode === 'cursor' ? undefined : lastWatermark,
156
+ limit: 50,
157
+ }
158
+ const result = (await bridge.send({
159
+ type: 'evaluate',
160
+ // hand-marshal the query through evaluate — the bridge already
161
+ // exposes `SootSim.bridges.timeline.recent` as a sync method,
162
+ // but evaluate is the universally available shape and the cost
163
+ // of a string-baked function is negligible at 5Hz.
164
+ code: `(async () => {
165
+ const t = window.SootSim?.bridges?.timeline
166
+ if (!t) return { ok: false, error: 'timeline bridge missing' }
167
+ return { ok: true, result: await t.recent(${JSON.stringify(query)}) }
168
+ })()`,
169
+ })) as { ok: true; result: SootSimTimelineQueryResult } | { ok: false; error: string }
170
+ if (!result || !result.ok) {
171
+ console.error(
172
+ ` could not query timeline: ${result && 'error' in result ? result.error : 'unknown'}`,
173
+ )
174
+ process.exit(1)
175
+ }
176
+ const evts = result.result.events ?? []
177
+ for (const ev of evts) {
178
+ if (eventMatches(ev, { filter, equals })) {
179
+ const elapsed = Date.now() - startedAt
180
+ if (jsonOut) {
181
+ console.log(JSON.stringify({ found: true, elapsedMs: elapsed, event: ev }))
182
+ } else {
183
+ console.log(
184
+ ` ${kind} event after ${elapsed}ms${filter ? ` (filter: ${filter})` : ''}${equals ? ` (equals: ${equals})` : ''}`,
185
+ )
186
+ }
187
+ return
188
+ }
189
+ }
190
+ // advance the local watermark to the high-water mark we just
191
+ // saw so the next poll doesn't redundantly re-scan the same
192
+ // events.
193
+ if (result.result.watermark && result.result.watermark > lastWatermark) {
194
+ lastWatermark = result.result.watermark
195
+ }
196
+ await new Promise((r) => setTimeout(r, pollMs))
197
+ }
198
+
199
+ const elapsed = Date.now() - startedAt
200
+ if (jsonOut) {
201
+ console.log(
202
+ JSON.stringify({ found: false, elapsedMs: elapsed, kind, filter, equals }),
203
+ )
204
+ } else {
205
+ console.error(
206
+ ` ⚠ wait event ${kind} timed out after ${elapsed}ms${filter ? ` (filter: ${filter})` : ''}${equals ? ` (equals: ${equals})` : ''}`,
207
+ )
208
+ }
209
+ process.exit(1)
210
+ }
@@ -0,0 +1,24 @@
1
+ import { resolveMaxMsFlag } from './core'
2
+ import { waitForSootsimIdle } from './settling'
3
+ import type { WsBridge } from '../../ws-bridge'
4
+
5
+ // alias for `do settle` under the discoverable `wait` verb grouping.
6
+ // layout hash stability across 3 polls; --strict also requires worker
7
+ // animation flags to be clear.
8
+ export async function runWaitIdleSubcommand(opts: {
9
+ bridge: WsBridge
10
+ args: string[]
11
+ positional: string[]
12
+ }): Promise<void> {
13
+ const { bridge, args } = opts
14
+ const strict = args.includes('--strict')
15
+ const maxMs = resolveMaxMsFlag(args, 3000)
16
+ const { elapsed, settled } = await waitForSootsimIdle({ bridge, maxMs, strict })
17
+
18
+ if (settled) {
19
+ console.log(` idle in ${elapsed}ms`)
20
+ } else {
21
+ console.error(` ⚠ wait idle timed out after ${elapsed}ms (may still be animating)`)
22
+ process.exit(1)
23
+ }
24
+ }