rnxsim 0.1.313 → 0.1.315

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-CBU2WDKV.js} +2 -2
  126. package/dist-cli/chunks/{agent-wrapper-JJYYW2WH.js → agent-wrapper-UNB4R27L.js} +2 -2
  127. package/dist-cli/chunks/{app-fonts-IXRNQG6B.js → app-fonts-YMLRF3JD.js} +2 -2
  128. package/dist-cli/chunks/{assert-54T5SK5F.js → assert-5N6HUCL5.js} +2 -2
  129. package/dist-cli/chunks/{auth-FI5UDI45.js → auth-BHDR3A6G.js} +2 -2
  130. package/dist-cli/chunks/{beta-JV6UKADW.js → beta-LWUVCPYK.js} +2 -2
  131. package/dist-cli/chunks/camera-CNO6B2JZ.js +33 -0
  132. package/dist-cli/chunks/{chunk-ZMJD5GEC.js → chunk-25VCR44B.js} +1 -1
  133. package/dist-cli/chunks/{chunk-WUSWBCWA.js → chunk-26CSIR76.js} +8 -9
  134. package/dist-cli/chunks/{chunk-WINYQ44O.js → chunk-26WMWUH4.js} +1 -1
  135. package/dist-cli/chunks/{chunk-BTWORNNG.js → chunk-2JZ53D4A.js} +1 -1
  136. package/dist-cli/chunks/{chunk-DCEMHR2Y.js → chunk-2POODCTW.js} +2 -2
  137. package/dist-cli/chunks/{chunk-5TEF3ET3.js → chunk-4LTBK3ZN.js} +2 -2
  138. package/dist-cli/chunks/{chunk-NMF2ZMZQ.js → chunk-54MIRTFE.js} +4 -4
  139. package/dist-cli/chunks/{chunk-WEXDAC74.js → chunk-56H6EVO4.js} +2 -2
  140. package/dist-cli/chunks/{chunk-F5ZRSS3C.js → chunk-657SQG4D.js} +1 -1
  141. package/dist-cli/chunks/{chunk-IJ5CAZZC.js → chunk-6ZMRJPKI.js} +1 -1
  142. package/dist-cli/chunks/{chunk-W6K4EFPH.js → chunk-AOGGOC4V.js} +2 -2
  143. package/dist-cli/chunks/chunk-BPCDO7ST.js +15 -0
  144. package/dist-cli/chunks/{chunk-D4FFVGI5.js → chunk-C5O5CTXJ.js} +1 -1
  145. package/dist-cli/chunks/{chunk-RTN5C5RL.js → chunk-CMM64PPR.js} +1 -1
  146. package/dist-cli/chunks/{chunk-BBULZ7CG.js → chunk-D3M36D7O.js} +62 -87
  147. package/dist-cli/chunks/{chunk-5DHC6KHQ.js → chunk-DNCNFZ7V.js} +1 -1
  148. package/dist-cli/chunks/{chunk-YIFT42WN.js → chunk-FNTTIR2P.js} +2 -2
  149. package/dist-cli/chunks/chunk-H362IX2I.js +4 -0
  150. package/dist-cli/chunks/{chunk-IJO63TDP.js → chunk-J6HMTUZH.js} +2 -2
  151. package/dist-cli/chunks/{chunk-RSZWCKNT.js → chunk-JZ6VTZ5B.js} +3 -3
  152. package/dist-cli/chunks/{chunk-MJRLLB4R.js → chunk-KQSJVNFE.js} +4 -4
  153. package/dist-cli/chunks/chunk-L4JZKVCC.js +6 -0
  154. package/dist-cli/chunks/{chunk-VFCMSYZK.js → chunk-LP2BTEJ6.js} +2 -2
  155. package/dist-cli/chunks/{chunk-2YR5BGA5.js → chunk-LT6UC7I3.js} +2 -2
  156. package/dist-cli/chunks/{chunk-OVFJFXUD.js → chunk-MCKQH4AM.js} +2 -2
  157. package/dist-cli/chunks/{chunk-TZFFR3SD.js → chunk-MF7WUHQ3.js} +2 -2
  158. package/dist-cli/chunks/{chunk-46EUUFJ5.js → chunk-MSSY4EEZ.js} +1 -1
  159. package/dist-cli/chunks/{chunk-WMIIKMGK.js → chunk-N5ZCYP5F.js} +2 -2
  160. package/dist-cli/chunks/{chunk-3NV2NCNX.js → chunk-NCKAD3V7.js} +2 -2
  161. package/dist-cli/chunks/{chunk-VNQEB4L7.js → chunk-O2KRHVTU.js} +2 -2
  162. package/dist-cli/chunks/{chunk-OZSSI4WN.js → chunk-OF2HTH25.js} +2 -2
  163. package/dist-cli/chunks/{chunk-GGRX24GF.js → chunk-OXS2IWO2.js} +2 -2
  164. package/dist-cli/chunks/{chunk-WWZIXIRD.js → chunk-PVL7XSPN.js} +1 -1
  165. package/dist-cli/chunks/{chunk-DZS6WPUI.js → chunk-PVPAP3TV.js} +1 -1
  166. package/dist-cli/chunks/{chunk-5YJCOWCH.js → chunk-PXNAZYGV.js} +1 -1
  167. package/dist-cli/chunks/chunk-QR3HTFRB.js +5 -0
  168. package/dist-cli/chunks/{chunk-5TPRP5QT.js → chunk-QR7MWAWE.js} +1 -1
  169. package/dist-cli/chunks/chunk-RJIEZ2NY.js +4 -0
  170. package/dist-cli/chunks/{chunk-UC6U3MML.js → chunk-RVM4EZ4E.js} +2 -2
  171. package/dist-cli/chunks/{chunk-2D2UPBBR.js → chunk-SZKSFMWS.js} +1 -1
  172. package/dist-cli/chunks/{chunk-7GN3LVWB.js → chunk-TEL7G234.js} +2 -2
  173. package/dist-cli/chunks/{chunk-GASE6UBA.js → chunk-U2HNIHBY.js} +1 -1
  174. package/dist-cli/chunks/chunk-UROXJVY3.js +27 -0
  175. package/dist-cli/chunks/{chunk-HI5TFJWN.js → chunk-WM32MGTJ.js} +2 -2
  176. package/dist-cli/chunks/{chunk-VZXWHRUZ.js → chunk-WMZ7NJAO.js} +89 -133
  177. package/dist-cli/chunks/{chunk-XEVZYVIW.js → chunk-WUXCH6WN.js} +10 -9
  178. package/dist-cli/chunks/chunk-XOXGC3EH.js +9 -0
  179. package/dist-cli/chunks/chunk-XSSHENNY.js +4 -0
  180. package/dist-cli/chunks/{chunk-OHAZNXLK.js → chunk-XZ6O67AR.js} +1 -1
  181. package/dist-cli/chunks/{chunk-QKDWYITG.js → chunk-Y2MWQKOS.js} +3 -3
  182. package/dist-cli/chunks/{chunk-YDGQTMQL.js → chunk-YY24QI25.js} +1 -1
  183. package/dist-cli/chunks/{chunk-WF3T4SVI.js → chunk-ZWX6DDIS.js} +2 -2
  184. package/dist-cli/chunks/{cleanup-P27PA6JI.js → cleanup-F3NUJ3SL.js} +2 -2
  185. package/dist-cli/chunks/cli-version-XIW6O3FG.js +4 -0
  186. package/dist-cli/chunks/{compat-ZD65FED3.js → compat-AX2SEPTH.js} +2 -2
  187. package/dist-cli/chunks/{config-XMJRNM2A.js → config-4QQO5JVU.js} +2 -2
  188. package/dist-cli/chunks/{control-KMIQT3QP.js → control-OCKKSOWA.js} +2 -2
  189. package/dist-cli/chunks/daemon-N5VLMYKQ.js +4 -0
  190. package/dist-cli/chunks/{debug-PT4HOP7N.js → debug-T6MWVEVX.js} +5 -5
  191. package/dist-cli/chunks/{desktop-S3FG72AK.js → desktop-NCW7R7CB.js} +3 -3
  192. package/dist-cli/chunks/{detox-B3D4IFCN.js → detox-G6GITO2X.js} +2 -2
  193. package/dist-cli/chunks/{device-XBNDSB2R.js → device-7BTEJCTH.js} +2 -2
  194. package/dist-cli/chunks/{diagnose-HMQXJE5N.js → diagnose-SC5LACE3.js} +2 -2
  195. package/dist-cli/chunks/{disk-cleanup-BLCZ5BSZ.js → disk-cleanup-KK56QQZA.js} +2 -2
  196. package/dist-cli/chunks/drivers-NPGVBWMG.js +4 -0
  197. package/dist-cli/chunks/{film-BJGTBYZB.js → film-WGYEHGIA.js} +3 -3
  198. package/dist-cli/chunks/flow-KQBIBM3N.js +4 -0
  199. package/dist-cli/chunks/help-G7CAKI65.js +4 -0
  200. package/dist-cli/chunks/{hidden-runtime-alias-ANOYADHM.js → hidden-runtime-alias-CWTZWH3L.js} +2 -2
  201. package/dist-cli/chunks/home-paths-EBT4XHAS.js +4 -0
  202. package/dist-cli/chunks/inspect-CCPZXVDU.js +4 -0
  203. package/dist-cli/chunks/install-desktop-NZ3VGTQZ.js +4 -0
  204. package/dist-cli/chunks/{login-FJ737MWG.js → login-GSYOTGVQ.js} +4 -4
  205. package/dist-cli/chunks/{logout-ZCNMMHMY.js → logout-JPRIR7YK.js} +2 -2
  206. package/dist-cli/chunks/{maestro-SZTNKLDF.js → maestro-LXKDNO5M.js} +3 -3
  207. package/dist-cli/chunks/{maestro-generate-DCFAIZ4H.js → maestro-generate-5ZIKOU53.js} +3 -3
  208. package/dist-cli/chunks/{mode-GRMQCRXR.js → mode-N3QHXCES.js} +2 -2
  209. package/dist-cli/chunks/{optional-demo-registry-W36EWFFB.js → optional-demo-registry-XDH6X24N.js} +2 -2
  210. package/dist-cli/chunks/{perf-QYBAAUZG.js → perf-VOPL26FM.js} +2 -2
  211. package/dist-cli/chunks/{permissions-3QCQ6VF4.js → permissions-CZFKM4UD.js} +2 -2
  212. package/dist-cli/chunks/{record-QPPC2S4E.js → record-FODSOGMA.js} +3 -3
  213. package/dist-cli/chunks/{report-issue-7NMFP4HK.js → report-issue-FBPCZKOV.js} +2 -2
  214. package/dist-cli/chunks/reset-3H35GFO3.js +4 -0
  215. package/dist-cli/chunks/runtime-4OVFRXV5.js +4 -0
  216. package/dist-cli/chunks/{screenshot-command-67AECJFB.js → screenshot-command-7S3FYVO7.js} +7 -7
  217. package/dist-cli/chunks/{screenshot-layers-ASWBYPJL.js → screenshot-layers-CDYHPNC3.js} +3 -3
  218. package/dist-cli/chunks/{screenshots-capture-PXA3HFQK.js → screenshots-capture-EBIIY2YU.js} +2 -2
  219. package/dist-cli/chunks/serve-NELNSCZP.js +44 -0
  220. package/dist-cli/chunks/{setup-7DWPMRSB.js → setup-DF5ZDDIV.js} +2 -2
  221. package/dist-cli/chunks/{skills-S3Y22TUA.js → skills-E2QZF6WZ.js} +2 -2
  222. package/dist-cli/chunks/state-MBZDKZ7U.js +14 -0
  223. package/dist-cli/chunks/{storage-XUIMJWAJ.js → storage-J5BMNVXM.js} +6 -6
  224. package/dist-cli/chunks/store-U3DXC7HH.js +4 -0
  225. package/dist-cli/chunks/telemetry-XIPJVT5E.js +4 -0
  226. package/dist-cli/chunks/{timeline-TMPLQPSP.js → timeline-MC6N5BHY.js} +2 -2
  227. package/dist-cli/chunks/{upgrade-7HDSIM7K.js → upgrade-7UTU53DH.js} +2 -2
  228. package/dist-cli/chunks/upload-2M2KOTYI.js +4 -0
  229. package/dist-cli/chunks/version-VNACNYQK.js +6 -0
  230. package/dist-cli/chunks/{web-DG3WBYD3.js → web-MGJ2TUKR.js} +2 -2
  231. package/dist-cli/chunks/{what-happened-XFVUTZR7.js → what-happened-7DLKH3OD.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,17 @@
1
+ // `sootsim get count` — total SootSimNode count in the live tree.
2
+
3
+ import { inspectNodeCount } from './core'
4
+ import { printJson, wantsJson } from './shared'
5
+ import type { WsBridge } from '../../ws-bridge'
6
+
7
+ export async function runCountSubcommand(
8
+ bridge: WsBridge,
9
+ opts: { args: string[] } = { args: [] },
10
+ ): Promise<void> {
11
+ const result = await inspectNodeCount(bridge)
12
+ if (wantsJson(opts.args)) {
13
+ printJson(result)
14
+ return
15
+ }
16
+ console.log(` nodes: ${result.nodes}`)
17
+ }
@@ -0,0 +1,192 @@
1
+ // `sootsim describe` — dump the visible-UI tree with layout + style annotations.
2
+ //
3
+ // see packages/sootsim-engine/src/test-bridge.ts for the actual dumpTree impl
4
+ // that powers this. the CLI-side job is just: optional preflight for in-flight
5
+ // screen transitions, call dumpTree with the right flags, and pretty-print.
6
+
7
+ import { maybeHint } from '../../hints'
8
+ import { inspectDescribe, type DescribeDumpOptions } from './core'
9
+ import {
10
+ maybeWaitForStartedScreenTransitions,
11
+ printJson,
12
+ sleep,
13
+ wantsJson,
14
+ } from './shared'
15
+ import type { WsBridge } from '../../ws-bridge'
16
+
17
+ export interface DescribeOptions {
18
+ bridge: WsBridge
19
+ args: string[]
20
+ positional: string[]
21
+ }
22
+
23
+ function flagValue(args: string[], name: string): string | null {
24
+ const i = args.indexOf(name)
25
+ return i >= 0 && i + 1 < args.length ? args[i + 1] : null
26
+ }
27
+
28
+ export async function runDescribeSubcommand(opts: DescribeOptions): Promise<void> {
29
+ const { bridge, args, positional } = opts
30
+ const verbose = args.includes('--verbose') || args.includes('-v')
31
+ const jsonOut = wantsJson(args)
32
+ const verboseDescribeTransitions = verbose && !jsonOut
33
+ const watch = args.includes('--watch') || args.includes('-w')
34
+ const watchInterval = 1000
35
+ const compact = args.includes('--compact')
36
+ const hideXy = args.includes('--no-xy')
37
+ // when set, strip rows annotated `(clipped:left|right|top|bottom)` from the
38
+ // output. dumpTree marks anything that overflows the viewport so search
39
+ // context is preserved, but the rows balloon the output on screens like
40
+ // 3pc's profile (off-screen list items + edge-pulled chrome). this flag is
41
+ // a client-side filter so we don't need a roundtrip to dumpTree.
42
+ const hideClipped = args.includes('--no-clipped')
43
+ // by default describe hides nodes that sit fully behind a modal/sheet/popover
44
+ // scrim — they aren't reachable, and listing them as `(tap)` misleads agents
45
+ // into tapping content that's actually occluded. --include-occluded surfaces
46
+ // them annotated `(behind)`.
47
+ const includeOccluded = args.includes('--include-occluded')
48
+
49
+ const testIdLike = flagValue(args, '--testid-like')
50
+ const onlyGlob = flagValue(args, '--only')
51
+ const subtreeRoot = flagValue(args, '--subtree')
52
+
53
+ // bare positional still works — routes to --only if it looks globby,
54
+ // otherwise preserves the old substring-match behavior as `filter`. guard
55
+ // against a flag-looking token leaking in as a filter: an unrecognized
56
+ // `--flag` that the arg parser didn't strip must never become a text filter
57
+ // (that's how `describe --compact` used to match nothing).
58
+ const positionalFilter =
59
+ positional[1] && !positional[1].startsWith('-') ? positional[1] : undefined
60
+ const bareIsGlob = positionalFilter ? /[*?]/.test(positionalFilter) : false
61
+ const filter = !bareIsGlob && !onlyGlob ? positionalFilter : undefined
62
+ const effectiveOnlyGlob = onlyGlob ?? (bareIsGlob ? positionalFilter : undefined)
63
+
64
+ const runOnce = async () => {
65
+ await maybeWaitForStartedScreenTransitions(bridge, {
66
+ verbose: verboseDescribeTransitions,
67
+ })
68
+ const dumpOpts: DescribeDumpOptions = {
69
+ describe: true,
70
+ verbose,
71
+ filter: filter || '',
72
+ testIdLike: testIdLike || undefined,
73
+ onlyGlob: effectiveOnlyGlob || undefined,
74
+ subtreeRoot: subtreeRoot || undefined,
75
+ compact,
76
+ hideXy,
77
+ includeOccluded,
78
+ // no terminal to fit, so nothing gets clipped on the way out
79
+ fullText: jsonOut,
80
+ }
81
+ const result = await inspectDescribe(bridge, dumpOpts)
82
+ const tree = result?.tree
83
+ const shell = result?.shell
84
+ const keyboard = result?.keyboard
85
+
86
+ if (jsonOut) {
87
+ printJson({ shell, tree: tree ?? '', keyboard })
88
+ return
89
+ }
90
+
91
+ // show shell state in header if available
92
+ if (shell && typeof shell === 'object') {
93
+ const shellParts = [
94
+ shell.state ? `state=${shell.state}` : null,
95
+ shell.activeApp ? `app=${shell.activeApp}` : null,
96
+ shell.showSwitcher ? 'switcher' : null,
97
+ shell.switcherPhase && shell.switcherPhase !== 'idle'
98
+ ? `phase=${shell.switcherPhase}`
99
+ : null,
100
+ ].filter(Boolean)
101
+ if (shellParts.length > 0) {
102
+ console.log(` shell: ${shellParts.join(' ')}`)
103
+ }
104
+ }
105
+
106
+ if (typeof tree === 'string' && tree.startsWith('__SUBTREE_NOT_FOUND__:')) {
107
+ const missing = tree.slice('__SUBTREE_NOT_FOUND__:'.length)
108
+ console.log(` subtree root not found: ${missing}`)
109
+ maybeHint('subtree-root-not-found', missing)
110
+ return
111
+ }
112
+
113
+ if (!tree) {
114
+ const nodeCount = result?.nodeCount ?? 0
115
+ console.log(' no matching nodes found')
116
+ const anyFilterActive = filter || testIdLike || effectiveOnlyGlob || subtreeRoot
117
+ if (!anyFilterActive && nodeCount < 10) {
118
+ maybeHint('app-still-loading', nodeCount)
119
+ }
120
+ return
121
+ }
122
+
123
+ let rendered = tree
124
+ let hiddenClippedCount = 0
125
+ if (hideClipped && typeof rendered === 'string') {
126
+ const lines = rendered.split('\n')
127
+ const kept: string[] = []
128
+ for (const line of lines) {
129
+ if (line.includes('(clipped:')) {
130
+ hiddenClippedCount += 1
131
+ continue
132
+ }
133
+ kept.push(line)
134
+ }
135
+ rendered = kept.join('\n')
136
+ }
137
+ console.log(rendered)
138
+ if (hiddenClippedCount > 0) {
139
+ console.log(
140
+ ` ${hiddenClippedCount} clipped row(s) hidden (omit --no-clipped to see)`,
141
+ )
142
+ }
143
+
144
+ const anyFilterActive = filter || testIdLike || effectiveOnlyGlob || subtreeRoot
145
+ const searchFilterActive = filter || testIdLike || effectiveOnlyGlob
146
+ if (searchFilterActive && !watch) {
147
+ maybeHint('describe-filter-context')
148
+ }
149
+
150
+ // when no filter was active and the tree got long, nudge once per
151
+ // session toward the narrowing flags — this is exactly the case the
152
+ // user flagged where "which row is the swap sheet" got buried under
153
+ // 100+ lines of home content.
154
+ if (!anyFilterActive && !watch) {
155
+ const lineCount = tree.split('\n').length
156
+ if (lineCount >= 80) {
157
+ maybeHint('describe-use-filters')
158
+ }
159
+ }
160
+
161
+ // keyboard summary — only when visible. rendered as a footer so it
162
+ // doesn't bury the tree on forms with non-trivial UI above the input.
163
+ if (keyboard && keyboard.visible) {
164
+ const spec = keyboard.spec
165
+ const kbParts = [
166
+ spec?.keyboardType ? `type=${spec.keyboardType}` : null,
167
+ spec?.returnKeyType && spec.returnKeyType !== 'default'
168
+ ? `return=${spec.returnKeyType}`
169
+ : null,
170
+ keyboard.mode !== 'letters' ? `mode=${keyboard.mode}` : null,
171
+ keyboard.shifted ? 'shift' : null,
172
+ keyboard.capsLock ? 'caps' : null,
173
+ spec?.autoCapitalize && spec.autoCapitalize !== 'sentences'
174
+ ? `autoCap=${spec.autoCapitalize}`
175
+ : null,
176
+ keyboard.accessoryBarId ? `accessory=${keyboard.accessoryBarId}` : null,
177
+ ].filter(Boolean)
178
+ console.log(`\n keyboard: ${kbParts.join(' ') || 'visible'}`)
179
+ }
180
+ }
181
+
182
+ if (watch) {
183
+ console.log(' watching... (Ctrl+C to stop)\n')
184
+ while (true) {
185
+ console.clear()
186
+ await runOnce()
187
+ await sleep(watchInterval)
188
+ }
189
+ } else {
190
+ await runOnce()
191
+ }
192
+ }
@@ -0,0 +1,23 @@
1
+ // env vars that indicate the CLI is being driven by an agent/automation rather
2
+ // than a human at a terminal. when any is present, auto-waits get a longer
3
+ // budget and log more aggressively, because an agent can't see mid-animation
4
+ // flicker the way a human can.
5
+ const AGENT_ENV_VARS = [
6
+ 'SOOTSIM_AGENT',
7
+ 'TM_SESSION',
8
+ 'CLAUDECODE',
9
+ 'CLAUDE_CODE_ENTRYPOINT',
10
+ 'CLAUDE_CODE_SESSION_ID',
11
+ 'CODEX_THREAD_ID',
12
+ 'CURSOR_TRACE_ID',
13
+ 'AIDER_MODEL',
14
+ ]
15
+
16
+ export function isAgentEnv(): boolean {
17
+ if (process.env.SOOTSIM_AGENT === '0') return false
18
+ for (const name of AGENT_ENV_VARS) {
19
+ const v = process.env[name]
20
+ if (v && v.trim() && v !== '0') return true
21
+ }
22
+ return false
23
+ }
@@ -0,0 +1,171 @@
1
+ import { maybeHint } from '../../hints'
2
+ import { inspectFind, rankInteractive, tapCommandForNode } from './core'
3
+ import { printJson, wantsJson } from './shared'
4
+ import type { WsBridge } from '../../ws-bridge'
5
+
6
+ // unified finder: one verb for text/testid/role/type/pressable/visible.
7
+ // --testid (not --id) keeps element lookup distinct from target selection.
8
+ // mode resolution + the evaluated JS live in inspect/core.ts (shared with the
9
+ // agent); this handler only parses flags and formats the result for the CLI.
10
+ export async function runFindSubcommand(opts: {
11
+ bridge: WsBridge
12
+ args: string[]
13
+ effectiveArgs: string[]
14
+ positional: string[]
15
+ inspectUsage: (name: string, tail: string) => string
16
+ }): Promise<void> {
17
+ const { bridge, args, effectiveArgs, positional, inspectUsage } = opts
18
+
19
+ const findFlag = (name: string): string | null => {
20
+ const i = effectiveArgs.indexOf(name)
21
+ return i >= 0 && i + 1 < effectiveArgs.length ? effectiveArgs[i + 1] : null
22
+ }
23
+ const hasFlag = (name: string): boolean => effectiveArgs.includes(name)
24
+
25
+ const testId = findFlag('--testid') || findFlag('--test-id')
26
+ const role = findFlag('--role')
27
+ const typeFlag = findFlag('--type')
28
+ const textFlag = findFlag('--text')
29
+ const pressable = hasFlag('--pressable')
30
+ const visible = hasFlag('--visible')
31
+ // ranked-tappable list — same data source as --pressable, but the
32
+ // output prioritises high-signal targets the agent likely wants to
33
+ // tap next (visible, has text or testID, has accessibilityLabel),
34
+ // and emits a copy-paste `sootsim do` command per row so the agent
35
+ // doesn't have to re-form a tap from raw fields.
36
+ const interactive = hasFlag('--interactive-targets') || hasFlag('--actions')
37
+ const bareText =
38
+ !testId && !role && !typeFlag && !textFlag && !pressable && !visible && !interactive
39
+ ? positional[1]
40
+ : null
41
+ const textQuery = textFlag ?? bareText
42
+
43
+ const found = await inspectFind(bridge, {
44
+ testId,
45
+ role,
46
+ type: typeFlag,
47
+ text: textQuery,
48
+ pressable,
49
+ visible,
50
+ interactive,
51
+ })
52
+ if (!found) {
53
+ console.error(
54
+ inspectUsage(
55
+ 'find',
56
+ '<text> | --text <t> | --testid <id> | --role <r> | --type <t> | --pressable | --visible | --interactive-targets',
57
+ ),
58
+ )
59
+ process.exit(1)
60
+ }
61
+ const { mode, result } = found
62
+ const jsonOut = wantsJson(args)
63
+ // --verbose / --dump: emit full node JSON per result instead of just the
64
+ // one-line summary. the default summary is compact for scannability; when
65
+ // you already know the match and need to copy testID / layout / a11y
66
+ // props, --verbose saves the round-trip through `get node <id>`.
67
+ const verbose = args.includes('--verbose') || args.includes('--dump')
68
+
69
+ if (jsonOut) {
70
+ if (mode === 'interactive-targets' && Array.isArray(result)) {
71
+ // include the `tap` command per row when emitting JSON for agents
72
+ // — the human renderer does the same. tooling that drives sootsim
73
+ // can pick the highest-scored entry and execute its `tap` field.
74
+ printJson(rankInteractive(result).map((n) => ({ ...n, tap: tapCommandForNode(n) })))
75
+ } else {
76
+ printJson(result ?? null)
77
+ }
78
+ } else if (Array.isArray(result)) {
79
+ if (result.length === 0) {
80
+ console.log(` no ${mode} nodes found`)
81
+ const nodeCount = await bridge.send({
82
+ type: 'evaluate',
83
+ code: '(async () => (await window.__sootsimTest?.getNodeCount?.()) || 0)()',
84
+ })
85
+ if (typeof nodeCount === 'number' && nodeCount < 10) {
86
+ maybeHint('app-still-loading', nodeCount)
87
+ }
88
+ } else if (mode === 'interactive-targets') {
89
+ const ranked = rankInteractive(result)
90
+ console.log(
91
+ ` found ${ranked.length} interactive target${ranked.length === 1 ? '' : 's'} (sorted by score):`,
92
+ )
93
+ for (const n of ranked.slice(0, 20)) {
94
+ const loc = n.absolutePosition
95
+ ? `@(${Math.round(n.absolutePosition.x)},${Math.round(n.absolutePosition.y)})`
96
+ : ''
97
+ const size = n.layout
98
+ ? `${Math.round(n.layout.width)}x${Math.round(n.layout.height)}`
99
+ : '?x?'
100
+ const text = n.text ? ` "${n.text.slice(0, 30)}"` : ''
101
+ const tid = n.testID ? ` #${n.testID}` : ''
102
+ const label = n.accessibilityLabel
103
+ ? ` ⓘ"${String(n.accessibilityLabel).slice(0, 24)}"`
104
+ : ''
105
+ const roleName = n.accessibilityRole ? `[${n.accessibilityRole}]` : n.type
106
+ const tapCmd = tapCommandForNode(n)
107
+ console.log(` ${roleName}${text}${label}${tid} ${size} ${loc}`)
108
+ console.log(` → ${tapCmd}`)
109
+ if (verbose) console.log(indent(JSON.stringify(n, null, 2), ' '))
110
+ }
111
+ if (ranked.length > 20) console.log(` ... and ${ranked.length - 20} more`)
112
+ } else {
113
+ console.log(
114
+ ` found ${result.length} node${result.length === 1 ? '' : 's'} (${mode}):`,
115
+ )
116
+ for (const n of result.slice(0, 20)) {
117
+ const loc = n.absolutePosition
118
+ ? `@(${Math.round(n.absolutePosition.x)},${Math.round(n.absolutePosition.y)})`
119
+ : ''
120
+ const size = n.layout
121
+ ? `${Math.round(n.layout.width)}x${Math.round(n.layout.height)}`
122
+ : '?x?'
123
+ const text = n.text ? ` "${n.text.slice(0, 30)}"` : ''
124
+ const tid = n.testID ? ` #${n.testID}` : ''
125
+ const tap = n.pressable ? ' (tap)' : ''
126
+ const roleName = n.accessibilityRole ? `[${n.accessibilityRole}]` : n.type
127
+ console.log(` ${roleName}${text}${tid} ${size} ${loc}${tap}`)
128
+ if (verbose) console.log(indent(JSON.stringify(n, null, 2), ' '))
129
+ }
130
+ if (result.length > 20) console.log(` ... and ${result.length - 20} more`)
131
+ }
132
+ } else if (result == null) {
133
+ const query = textQuery || testId || role || typeFlag || ''
134
+ console.log(` not found: ${query || mode}`)
135
+ if (testId) {
136
+ maybeHint('wait-selector-for-missing-testid', testId)
137
+ }
138
+ } else {
139
+ interface InspectNode {
140
+ type?: string
141
+ absolutePosition?: { x: number; y: number }
142
+ layout?: { width: number; height: number }
143
+ text?: string
144
+ testID?: string
145
+ pressable?: boolean
146
+ accessibilityRole?: string
147
+ }
148
+ const n = result as InspectNode
149
+ if (n.type && n.absolutePosition) {
150
+ const loc = `@(${Math.round(n.absolutePosition.x)},${Math.round(n.absolutePosition.y)})`
151
+ const size = n.layout
152
+ ? `${Math.round(n.layout.width)}x${Math.round(n.layout.height)}`
153
+ : '?x?'
154
+ const text = n.text ? ` "${n.text.slice(0, 40)}"` : ''
155
+ const tid = n.testID ? ` #${n.testID}` : ''
156
+ const tap = n.pressable ? ' (tap)' : ''
157
+ const roleName = n.accessibilityRole ? `[${n.accessibilityRole}]` : n.type
158
+ console.log(` ${roleName}${text}${tid} ${size} ${loc}${tap}`)
159
+ if (verbose) console.log(indent(JSON.stringify(n, null, 2), ' '))
160
+ } else {
161
+ console.log(JSON.stringify(result, null, 2))
162
+ }
163
+ }
164
+ }
165
+
166
+ function indent(text: string, prefix: string): string {
167
+ return text
168
+ .split('\n')
169
+ .map((line) => prefix + line)
170
+ .join('\n')
171
+ }
@@ -0,0 +1,39 @@
1
+ // `sootsim get-layout` — bounding boxes for visible meaningful elements on the
2
+ // current screen. focused on the layout numbers a designer checks: on-screen
3
+ // box (x/y/w/h). pass --styling for computed borderRadius, per-side padding,
4
+ // fontSize, visual style fields, and native rendered text contrast. pass
5
+ // --filter <text> to narrow to the elements whose text/testID/role/selector
6
+ // match — fewer rows, full detail on each, which is how you read one area's
7
+ // styling without the whole screen.
8
+ //
9
+ // the extraction + formatting live in the shared `./core` kernel
10
+ // (inspectGetLayout / formatLayoutElements) so this CLI command and the agent's
11
+ // app_get_layout tool produce identical output. the CLI-side job is just:
12
+ // optional preflight for in-flight screen transitions, call the kernel, print.
13
+
14
+ import { formatLayoutElements, inspectGetLayout } from './core'
15
+ import { maybeWaitForStartedScreenTransitions, printJson, wantsJson } from './shared'
16
+ import type { WsBridge } from '../../ws-bridge'
17
+
18
+ export async function runGetLayoutSubcommand(opts: {
19
+ bridge: WsBridge
20
+ args: string[]
21
+ }): Promise<void> {
22
+ const { bridge, args } = opts
23
+ const jsonOut = wantsJson(args)
24
+ await maybeWaitForStartedScreenTransitions(bridge, { verbose: !jsonOut })
25
+ const styling = args.includes('--styling')
26
+ const filterIndex = args.indexOf('--filter')
27
+ const filter = filterIndex >= 0 ? args[filterIndex + 1] : undefined
28
+ const elements = await inspectGetLayout(bridge, { styling, filter })
29
+ if (jsonOut) {
30
+ printJson({ count: elements.length, elements })
31
+ return
32
+ }
33
+ console.log(
34
+ ` layout (${elements.length} element${elements.length === 1 ? '' : 's'}):\n`,
35
+ )
36
+ // styling fields (including native rendered text contrast) are opt-in via
37
+ // --styling so the default inspect output stays scannable.
38
+ console.log(formatLayoutElements(elements, { styling }))
39
+ }
@@ -0,0 +1,52 @@
1
+ // `sootsim get keyboard` — prints the current iOS keyboard's full state.
2
+ //
3
+ // shows visibility, keyboardType, returnKeyType, autoCapitalize policy,
4
+ // active QWERTY sub-mode, shift + capsLock, and the mounted accessory bar
5
+ // id (if any). reads from `__sootsimKeyboard.getLayout()`, which is
6
+ // populated by shell-worker `keyboard.layout` broadcasts.
7
+
8
+ import { inspectKeyboard } from './core'
9
+ import type { WsBridge } from '../../ws-bridge'
10
+
11
+ export async function runKeyboardSubcommand(
12
+ bridge: WsBridge,
13
+ opts: { json?: boolean } = {},
14
+ ): Promise<void> {
15
+ const result = await inspectKeyboard(bridge)
16
+
17
+ if ('error' in result) {
18
+ console.error(result.error)
19
+ process.exit(1)
20
+ }
21
+
22
+ if (opts.json) {
23
+ console.log(JSON.stringify(result, null, 2))
24
+ return
25
+ }
26
+
27
+ const { visible, spec, mode, shifted, capsLock, accessoryBarId } = result
28
+ const lines: string[] = []
29
+ lines.push(`keyboard: ${visible ? 'visible' : 'hidden'}`)
30
+ if (spec) {
31
+ lines.push(` type: ${spec.keyboardType}`)
32
+ lines.push(` returnKey: ${spec.returnKeyType}`)
33
+ lines.push(` autoCap: ${spec.autoCapitalize}`)
34
+ lines.push(` autoCorrect: ${spec.autoCorrect ? 'on' : 'off'}`)
35
+ lines.push(` appearance: ${spec.keyboardAppearance}`)
36
+ if (spec.secureTextEntry) lines.push(` secureTextEntry: true`)
37
+ if (spec.enablesReturnKeyAutomatically) {
38
+ lines.push(
39
+ ` return: ${spec.currentTextIsEmpty ? 'disabled (empty)' : 'enabled'}`,
40
+ )
41
+ }
42
+ } else {
43
+ lines.push(` spec: <none> (shown via dev-tools with no TextInput)`)
44
+ }
45
+ lines.push(
46
+ ` mode: ${mode}${shifted ? ' (shifted)' : ''}${capsLock ? ' (caps)' : ''}`,
47
+ )
48
+ if (accessoryBarId) {
49
+ lines.push(` accessoryBar: ${accessoryBarId}`)
50
+ }
51
+ console.log(lines.join('\n'))
52
+ }
@@ -0,0 +1,58 @@
1
+ // `sootsim list` — connected sims, with the active one marked.
2
+
3
+ import { printConnectedSims } from '../control'
4
+ import { printJson, wantsJson } from './shared'
5
+ import type { WsBridge } from '../../ws-bridge'
6
+
7
+ export async function runListSubcommand(opts: {
8
+ bridge: WsBridge
9
+ simId: string | undefined
10
+ args: string[]
11
+ }): Promise<void> {
12
+ const sims = await opts.bridge.listSims()
13
+
14
+ // filter sims:
15
+ // --all show every sim including empty shells (default
16
+ // hides sims that have no bundle URL — they're
17
+ // usually idle host shells that overflow the
18
+ // list when several demos are open at once)
19
+ // --bundle <substring> only sims whose loaded url contains the substring
20
+ // --app-port <port> shorthand for `--bundle :<port>/`. NOT `--port`
21
+ // which is taken by the global bridge-port flag
22
+ // (see ws-bridge.ts parseBridgeCliArgs) — using
23
+ // --port here used to silently set the bridge
24
+ // port too, sending the WS connect to the wrong
25
+ // place and failing with "daemon is not running"
26
+ // --primary only the primary sim
27
+ const showAll = opts.args.includes('--all')
28
+ const bundleArg = opts.args.find((_, i) => opts.args[i - 1] === '--bundle')
29
+ const portArg = opts.args.find((_, i) => opts.args[i - 1] === '--app-port')
30
+ const primaryOnly = opts.args.includes('--primary')
31
+ const filtered = sims.filter((sim) => {
32
+ if (primaryOnly && !sim.isPrimary) return false
33
+ if (bundleArg && !(sim.url ?? '').includes(bundleArg)) return false
34
+ // a sim url looks like `http://localhost:5173/rn/8085?bundle=...` where
35
+ // `8085` is the metro/vite port the app is loaded from. the bundle url
36
+ // inside the `?bundle=` param is itself percent-encoded (`:8085/` ->
37
+ // `%3A8085%2F`), so the previous literal `:${port}/` substring check
38
+ // never matched. match the canonical `/rn/<port>` path segment instead.
39
+ if (portArg && !(sim.url ?? '').includes(`/rn/${portArg}`)) return false
40
+ if (!showAll && !bundleArg && !portArg && !primaryOnly) {
41
+ // hide host shells (no bundle loaded — only origin) unless they're
42
+ // the currently-selected sim
43
+ const hasBundle =
44
+ sim.url && (sim.url.includes('bundle=') || sim.url.includes('/index.bundle'))
45
+ if (!hasBundle && sim.id !== opts.simId) return false
46
+ }
47
+ return true
48
+ })
49
+
50
+ if (wantsJson(opts.args)) {
51
+ printJson(filtered.map((b) => ({ ...b, active: b.id === opts.simId })))
52
+ return
53
+ }
54
+ printConnectedSims(filtered, opts.simId)
55
+ if (filtered.length < sims.length && !wantsJson(opts.args)) {
56
+ console.log(` (${sims.length - filtered.length} more hidden — pass --all to show)`)
57
+ }
58
+ }