rnxsim 0.1.365 → 0.1.367

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 (240) hide show
  1. package/README.md +14 -0
  2. package/cli/bin.ts +18 -0
  3. package/cli/bridge-flow-runner.ts +16 -153
  4. package/cli/commands/assert.ts +1 -1
  5. package/cli/commands/box/checkout-plane.ts +126 -0
  6. package/cli/commands/box/rnx-command.ts +68 -0
  7. package/cli/commands/box.ts +200 -0
  8. package/cli/commands/control.ts +15 -76
  9. package/cli/commands/detox.ts +18 -1
  10. package/cli/commands/do-chain.ts +412 -0
  11. package/cli/commands/flow.ts +2 -11
  12. package/cli/commands/inspect/core.ts +302 -725
  13. package/cli/commands/inspect/describe.ts +3 -76
  14. package/cli/commands/inspect/find.ts +3 -6
  15. package/cli/commands/inspect/get-layout.ts +14 -1
  16. package/cli/commands/inspect/resolve-target.ts +41 -47
  17. package/cli/commands/inspect/shared.ts +28 -123
  18. package/cli/commands/inspect/tree.ts +6 -15
  19. package/cli/commands/inspect/wait-ready.ts +2 -2
  20. package/cli/commands/inspect/wait-selector.ts +4 -8
  21. package/cli/commands/inspect.ts +23 -17
  22. package/cli/commands/install-desktop.ts +44 -7
  23. package/cli/commands/maestro-generate.ts +0 -2
  24. package/cli/commands/platform.ts +68 -0
  25. package/cli/commands/state.ts +5 -2
  26. package/cli/outbound-endpoints.ts +6 -1
  27. package/cli/parse-args.ts +13 -2
  28. package/cli/ws-bridge.ts +30 -2
  29. package/detox/index.ts +37 -18
  30. package/dist-cli/bin.js +5 -5
  31. package/dist-cli/chunks/{agent-BVMNQFKF.js → agent-EKKIAKP5.js} +2 -2
  32. package/dist-cli/chunks/{agent-wrapper-B6YFXGC3.js → agent-wrapper-MHLQUGA6.js} +2 -2
  33. package/dist-cli/chunks/{app-fonts-MEI6R6XR.js → app-fonts-HN6ADRTQ.js} +2 -2
  34. package/dist-cli/chunks/{assert-JGHI53HV.js → assert-IMR6QCN6.js} +3 -3
  35. package/dist-cli/chunks/{auth-STEOXUS7.js → auth-27LPMQNH.js} +2 -2
  36. package/dist-cli/chunks/{beta-HT3FLS5T.js → beta-6RXBB5AI.js} +2 -2
  37. package/dist-cli/chunks/box-DORLF3GP.js +1709 -0
  38. package/dist-cli/chunks/{camera-ZCLFSLON.js → camera-2G7JZ5DL.js} +2 -2
  39. package/dist-cli/chunks/{chunk-RI3NS77J.js → chunk-3QZJLNYY.js} +1 -1
  40. package/dist-cli/chunks/{chunk-FHWZPUGC.js → chunk-3VBVEDDA.js} +1 -1
  41. package/dist-cli/chunks/{chunk-EAFKG5R3.js → chunk-4BWGLTKU.js} +2 -2
  42. package/dist-cli/chunks/chunk-4XSNO374.js +423 -0
  43. package/dist-cli/chunks/{chunk-GZEUDDAW.js → chunk-5DGMFUZG.js} +2 -2
  44. package/dist-cli/chunks/{chunk-TKCGP3DD.js → chunk-6DBS5EGX.js} +2 -2
  45. package/dist-cli/chunks/{chunk-HIDS3PYC.js → chunk-6SO6I3D5.js} +2 -2
  46. package/dist-cli/chunks/chunk-75EFOPXE.js +32 -0
  47. package/dist-cli/chunks/{chunk-FIXRGPUT.js → chunk-A5AJFVOF.js} +1 -1
  48. package/dist-cli/chunks/{chunk-2SFMW5K6.js → chunk-AM2ODYAN.js} +1 -1
  49. package/dist-cli/chunks/{chunk-BGRGP3QR.js → chunk-AVOROGA3.js} +1 -1
  50. package/dist-cli/chunks/chunk-AXMMLO7D.js +130 -0
  51. package/dist-cli/chunks/{chunk-XXNPISER.js → chunk-BSEXEYNX.js} +2 -2
  52. package/dist-cli/chunks/{chunk-67ATAITF.js → chunk-CM6ZKZW7.js} +2 -2
  53. package/dist-cli/chunks/{chunk-MR3YPNUI.js → chunk-DNVABCEC.js} +2 -2
  54. package/dist-cli/chunks/{chunk-5AS7S6HY.js → chunk-E33A4AIR.js} +2 -2
  55. package/dist-cli/chunks/{chunk-DBJZUCFP.js → chunk-EFMOXYDD.js} +2 -2
  56. package/dist-cli/chunks/{chunk-ZTWUNK45.js → chunk-EV4P5UBP.js} +2 -2
  57. package/dist-cli/chunks/{chunk-EVBQDQVY.js → chunk-G5IUIGGC.js} +1 -1
  58. package/dist-cli/chunks/chunk-GMLOCV3E.js +312 -0
  59. package/dist-cli/chunks/{chunk-DQFOHIRF.js → chunk-GRN2FHVW.js} +1 -1
  60. package/dist-cli/chunks/{chunk-CYIE65QN.js → chunk-HFFXPUEP.js} +1 -1
  61. package/dist-cli/chunks/chunk-HG6T4OOR.js +4 -0
  62. package/dist-cli/chunks/{chunk-GK7PGYFH.js → chunk-HNT2OXPZ.js} +1 -1
  63. package/dist-cli/chunks/chunk-IPEURNLI.js +24 -0
  64. package/dist-cli/chunks/chunk-IR44SFBA.js +5 -0
  65. package/dist-cli/chunks/chunk-JMSLXZFW.js +4 -0
  66. package/dist-cli/chunks/{chunk-WGNDJARS.js → chunk-JPXQKLNH.js} +1 -1
  67. package/dist-cli/chunks/chunk-K3EC6JDE.js +4 -0
  68. package/dist-cli/chunks/chunk-KHX65HAU.js +4 -0
  69. package/dist-cli/chunks/{chunk-MACI34ST.js → chunk-LWOVKHRC.js} +2 -2
  70. package/dist-cli/chunks/{chunk-QIQL7Y5V.js → chunk-MQN5DWTI.js} +2 -2
  71. package/dist-cli/chunks/{chunk-5VHWQGGF.js → chunk-N3BXQEUF.js} +1 -1
  72. package/dist-cli/chunks/{chunk-O2ORLK6W.js → chunk-N5OSV26B.js} +2 -2
  73. package/dist-cli/chunks/chunk-PHNURIH3.js +4 -0
  74. package/dist-cli/chunks/{chunk-JRPU5TFI.js → chunk-PP2KNVXP.js} +3 -3
  75. package/dist-cli/chunks/{chunk-UR345KM3.js → chunk-PROKNJVY.js} +2 -2
  76. package/dist-cli/chunks/chunk-QXLLY4IM.js +7 -0
  77. package/dist-cli/chunks/{chunk-2CXHDTQ5.js → chunk-RNQTOGM7.js} +1 -1
  78. package/dist-cli/chunks/{chunk-M5KJCLUW.js → chunk-RPNKKVBN.js} +27 -27
  79. package/dist-cli/chunks/{chunk-EFXNSJ55.js → chunk-RSNLI2NH.js} +2 -2
  80. package/dist-cli/chunks/{chunk-25BTVHK2.js → chunk-RZGHV2WI.js} +2 -2
  81. package/dist-cli/chunks/chunk-SGGL3AFV.js +497 -0
  82. package/dist-cli/chunks/chunk-SH5ETJKG.js +4 -0
  83. package/dist-cli/chunks/{chunk-MRNW4GVL.js → chunk-T6J4YP56.js} +2 -2
  84. package/dist-cli/chunks/{chunk-R4FQZXJC.js → chunk-TWUE7WF5.js} +2 -2
  85. package/dist-cli/chunks/{chunk-X5ZOZVHC.js → chunk-U44VEQRN.js} +1 -1
  86. package/dist-cli/chunks/{chunk-XRO5WQXP.js → chunk-URLVQHC3.js} +1 -1
  87. package/dist-cli/chunks/{chunk-JAFF4RAU.js → chunk-VANZFTUE.js} +1 -1
  88. package/dist-cli/chunks/{chunk-6BAF6GFS.js → chunk-VB2YBH46.js} +1 -1
  89. package/dist-cli/chunks/{chunk-O6P4N2JD.js → chunk-VFU2SJ7M.js} +2 -2
  90. package/dist-cli/chunks/{chunk-XPOTXF5Q.js → chunk-VUBJ6FXU.js} +15 -15
  91. package/dist-cli/chunks/{chunk-XR4KID25.js → chunk-W5PATILQ.js} +2 -2
  92. package/dist-cli/chunks/chunk-WI5V4TYG.js +38 -0
  93. package/dist-cli/chunks/{chunk-NEH4T67X.js → chunk-XTRHQAOV.js} +2 -2
  94. package/dist-cli/chunks/chunk-XWUTJLBM.js +27 -0
  95. package/dist-cli/chunks/{chunk-TULWPOAY.js → chunk-XYRUQQUR.js} +2 -2
  96. package/dist-cli/chunks/chunk-YBFB3RM7.js +4 -0
  97. package/dist-cli/chunks/chunk-YGQVQ2VG.js +4 -0
  98. package/dist-cli/chunks/{chunk-J66US2OZ.js → chunk-YINACV32.js} +1 -1
  99. package/dist-cli/chunks/chunk-YV7ERO2V.js +5 -0
  100. package/dist-cli/chunks/{cleanup-G4TEYVKY.js → cleanup-GVFHQ3KH.js} +2 -2
  101. package/dist-cli/chunks/cli-version-DPX5MA2M.js +4 -0
  102. package/dist-cli/chunks/{compat-EZ5NP32J.js → compat-AYRTXYAM.js} +2 -2
  103. package/dist-cli/chunks/{config-27M2DGCS.js → config-K7LOOV62.js} +2 -2
  104. package/dist-cli/chunks/connect-H3CNY4IO.js +4 -0
  105. package/dist-cli/chunks/control-SMHZ2CF5.js +4 -0
  106. package/dist-cli/chunks/daemon-TEZUNI73.js +4 -0
  107. package/dist-cli/chunks/{debug-EXU3D7PC.js → debug-ELGHG57T.js} +3 -3
  108. package/dist-cli/chunks/{desktop-SHHPZAWQ.js → desktop-4G7FK3MP.js} +3 -3
  109. package/dist-cli/chunks/detox-JKXGEJIZ.js +52 -0
  110. package/dist-cli/chunks/{device-4NI26HG6.js → device-IV3AR6KG.js} +2 -2
  111. package/dist-cli/chunks/{diagnose-P4SFXH6J.js → diagnose-3ERGF7EW.js} +2 -2
  112. package/dist-cli/chunks/{disk-cleanup-BUAKWYMY.js → disk-cleanup-ALFEVZ4B.js} +2 -2
  113. package/dist-cli/chunks/do-chain-K2B4OHM7.js +5 -0
  114. package/dist-cli/chunks/drivers-KJYXA3M3.js +4 -0
  115. package/dist-cli/chunks/env-2KF6IYZE.js +4 -0
  116. package/dist-cli/chunks/{film-Y2BAQINB.js → film-UQXJFAD5.js} +3 -3
  117. package/dist-cli/chunks/flow-ASTUH56E.js +4 -0
  118. package/dist-cli/chunks/help-SNNYFELA.js +4 -0
  119. package/dist-cli/chunks/{hidden-runtime-alias-U2MAY2XV.js → hidden-runtime-alias-VJCWCFVO.js} +2 -2
  120. package/dist-cli/chunks/{home-paths-PAJMZCOS.js → home-paths-EGUZZ6J6.js} +2 -2
  121. package/dist-cli/chunks/inspect-XANAADQT.js +4 -0
  122. package/dist-cli/chunks/install-desktop-JKQA7UY6.js +4 -0
  123. package/dist-cli/chunks/{login-D53XKBEO.js → login-6KQGD7HK.js} +2 -2
  124. package/dist-cli/chunks/{logout-HZQKLNUJ.js → logout-2FCOVLBC.js} +2 -2
  125. package/dist-cli/chunks/{maestro-XSS2KX6L.js → maestro-HPSSEVJD.js} +3 -3
  126. package/dist-cli/chunks/{maestro-generate-DNDQX3JQ.js → maestro-generate-KQQ6JHMR.js} +5 -5
  127. package/dist-cli/chunks/{mode-LEVGOKI3.js → mode-SH7KRZET.js} +2 -2
  128. package/dist-cli/chunks/{optional-demo-registry-2XZIJNDG.js → optional-demo-registry-NAMCAVFX.js} +2 -2
  129. package/dist-cli/chunks/{perf-OKMAXDLB.js → perf-LWKAHXUL.js} +2 -2
  130. package/dist-cli/chunks/{permissions-VG5TBUTN.js → permissions-EZTKWTXV.js} +2 -2
  131. package/dist-cli/chunks/platform-KSOJIVN5.js +6 -0
  132. package/dist-cli/chunks/privacy-EJULWP7L.js +4 -0
  133. package/dist-cli/chunks/{record-BQYGZOMV.js → record-2IC7BAQN.js} +3 -3
  134. package/dist-cli/chunks/{report-issue-HCPZESWE.js → report-issue-MMX6HOQO.js} +2 -2
  135. package/dist-cli/chunks/reset-PKFZA4NC.js +4 -0
  136. package/dist-cli/chunks/resolve-target-AFKU3BRE.js +4 -0
  137. package/dist-cli/chunks/runtime-XHWWPOIN.js +4 -0
  138. package/dist-cli/chunks/{screenshot-command-DWEYPJE7.js → screenshot-command-JYUXDVSA.js} +3 -3
  139. package/dist-cli/chunks/{screenshot-layers-5GMBXLO7.js → screenshot-layers-OGRY6AA3.js} +2 -2
  140. package/dist-cli/chunks/{screenshots-capture-5KCI7IZD.js → screenshots-capture-QHDN65XS.js} +2 -2
  141. package/dist-cli/chunks/{serve-QNKTA2HR.js → serve-W4BKAG4J.js} +3 -3
  142. package/dist-cli/chunks/setup-WJPMPSAC.js +4 -0
  143. package/dist-cli/chunks/sim-client-UOQGFBPO.js +4 -0
  144. package/dist-cli/chunks/{skills-DOEALD3X.js → skills-OPPXOD63.js} +2 -2
  145. package/dist-cli/chunks/state-TDAN7BOM.js +14 -0
  146. package/dist-cli/chunks/{storage-E7WUWN3E.js → storage-CFB2YGLY.js} +2 -2
  147. package/dist-cli/chunks/store-4TEED4ZU.js +4 -0
  148. package/dist-cli/chunks/telemetry-S6N7CV6G.js +4 -0
  149. package/dist-cli/chunks/{timeline-IKNUGQ2A.js → timeline-ASOOVP4C.js} +2 -2
  150. package/dist-cli/chunks/{upgrade-VS3OKXE2.js → upgrade-6MVJISHG.js} +2 -2
  151. package/dist-cli/chunks/upload-CBNIRFEW.js +4 -0
  152. package/dist-cli/chunks/{version-F4XHNTBZ.js → version-5GS7CWRL.js} +2 -2
  153. package/dist-cli/chunks/{web-F72724QL.js → web-QRRMYMV4.js} +2 -2
  154. package/dist-cli/chunks/welcome-33FXMAMB.js +5 -0
  155. package/dist-cli/chunks/{what-happened-APK7YKOV.js → what-happened-GAZJGKXX.js} +2 -2
  156. package/dist-cli/chunks/ws-bridge-DIXUAPLQ.js +4 -0
  157. package/dist-lib/agent-daemon-client.cjs +1 -1
  158. package/dist-lib/agent-events.cjs +1 -1
  159. package/dist-lib/agent-identity.cjs +1 -1
  160. package/dist-lib/agent-sessions.cjs +1 -1
  161. package/dist-lib/attached-projects.cjs +1 -1
  162. package/dist-lib/auth/shared-session.cjs +1 -1
  163. package/dist-lib/backend-origin.cjs +1 -1
  164. package/dist-lib/beta.cjs +1 -1
  165. package/dist-lib/beta.mjs +1 -1
  166. package/dist-lib/bridge-constants.cjs +1 -1
  167. package/dist-lib/bridge-contract-input.cjs +336 -0
  168. package/dist-lib/bridge-contract-input.mjs +303 -0
  169. package/dist-lib/bridge-contract.cjs +29 -1
  170. package/dist-lib/bridge-contract.mjs +19 -0
  171. package/dist-lib/cli-constants.cjs +1 -1
  172. package/dist-lib/config.cjs +1 -1
  173. package/dist-lib/detox/index.cjs +35 -19
  174. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  175. package/dist-lib/home-paths.cjs +1 -1
  176. package/dist-lib/host/bridge-host.cjs +5 -1
  177. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  178. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  179. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  180. package/dist-lib/host/replacement-module-handler.cjs +1 -1
  181. package/dist-lib/host/websocket-proxy.cjs +1 -1
  182. package/dist-lib/index.cjs +244 -594
  183. package/dist-lib/jump-to-source-babel.cjs +1 -1
  184. package/dist-lib/menu.cjs +1 -1
  185. package/dist-lib/menu.mjs +1 -1
  186. package/dist-lib/metro-production-bundle.cjs +1 -1
  187. package/dist-lib/metro-production-bundle.mjs +1 -1
  188. package/dist-lib/metro.cjs +1 -1
  189. package/dist-lib/profiles.cjs +1 -1
  190. package/dist-lib/public-brand.cjs +1 -1
  191. package/dist-lib/react-native-host-modules.cjs +1 -1
  192. package/dist-lib/react-native-host-modules.mjs +1 -1
  193. package/dist-lib/render-mode.cjs +1 -1
  194. package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
  195. package/dist-lib/sdk.cjs +303 -657
  196. package/dist-lib/sdk.mjs +301 -652
  197. package/dist-lib/skills.cjs +256 -339
  198. package/dist-lib/vite.cjs +1 -1
  199. package/package.json +10 -1
  200. package/skills/contrast/SKILL.md +3 -0
  201. package/skills/rnx-debug/SKILL.md +8 -0
  202. package/skills/rnx-test/SKILL.md +21 -0
  203. package/src/app-url.ts +40 -0
  204. package/src/bridge-contract-input.ts +359 -0
  205. package/src/bridge-contract.ts +121 -1
  206. package/src/host/bridge-host.ts +7 -12
  207. package/src/index.ts +7 -0
  208. package/src/sim-client.ts +24 -9
  209. package/dist-cli/chunks/chunk-2JFZ5Z7Q.js +0 -4
  210. package/dist-cli/chunks/chunk-4WFN4DFR.js +0 -5
  211. package/dist-cli/chunks/chunk-52ES4API.js +0 -38
  212. package/dist-cli/chunks/chunk-622E4JET.js +0 -767
  213. package/dist-cli/chunks/chunk-EFL2ASZO.js +0 -124
  214. package/dist-cli/chunks/chunk-IWPS3EKJ.js +0 -15
  215. package/dist-cli/chunks/chunk-MCHKBFUL.js +0 -4
  216. package/dist-cli/chunks/chunk-OSZ5HZSI.js +0 -24
  217. package/dist-cli/chunks/chunk-W3UIDI3W.js +0 -5
  218. package/dist-cli/chunks/chunk-XKRUNS7A.js +0 -32
  219. package/dist-cli/chunks/chunk-YE3M3YD4.js +0 -944
  220. package/dist-cli/chunks/chunk-ZCP4CRP6.js +0 -4
  221. package/dist-cli/chunks/chunk-ZGD7GZRI.js +0 -4
  222. package/dist-cli/chunks/chunk-ZGH5U4AJ.js +0 -27
  223. package/dist-cli/chunks/cli-version-ZM3PVKQ5.js +0 -4
  224. package/dist-cli/chunks/control-4QR43FXV.js +0 -4
  225. package/dist-cli/chunks/daemon-2U4CJ6AT.js +0 -4
  226. package/dist-cli/chunks/detox-RQ6FFSBS.js +0 -52
  227. package/dist-cli/chunks/drivers-OVCUPQTM.js +0 -4
  228. package/dist-cli/chunks/flow-ELTLKKZ3.js +0 -4
  229. package/dist-cli/chunks/help-CDEZAG2L.js +0 -4
  230. package/dist-cli/chunks/inspect-VVQRPJLS.js +0 -4
  231. package/dist-cli/chunks/install-desktop-YXSRKR35.js +0 -4
  232. package/dist-cli/chunks/privacy-MNRHREME.js +0 -4
  233. package/dist-cli/chunks/reset-R4E4W4YI.js +0 -4
  234. package/dist-cli/chunks/runtime-XRQZ7W34.js +0 -4
  235. package/dist-cli/chunks/setup-RNMM5GO5.js +0 -4
  236. package/dist-cli/chunks/state-3HH2IHIF.js +0 -14
  237. package/dist-cli/chunks/store-HUPZUCFG.js +0 -4
  238. package/dist-cli/chunks/telemetry-IJ53XR2Y.js +0 -4
  239. package/dist-cli/chunks/upload-A57NAAHF.js +0 -4
  240. package/dist-cli/chunks/welcome-B72RRMBW.js +0 -5
@@ -0,0 +1,200 @@
1
+ // `rnx box` — the box environment.
2
+ //
3
+ // bare `rnx box` is LOCAL and mounts the current checkout at /project. `rnx box
4
+ // create` provisions a durable cloud box. everything else in the namespace is
5
+ // cloud lifecycle. the cloud side is not built yet; those verbs fail by name
6
+ // and say what works instead, so nothing advertises a capability that always
7
+ // fails.
8
+
9
+ import path from 'node:path'
10
+ import { createInterface } from 'node:readline'
11
+ import { createHarnessShell, HarnessProjectFs, PROJECT_MOUNT } from '@contrast/harness'
12
+ import { rnxPublicBrand } from '../../src/public-brand'
13
+ import { findRepositoryPackageRoot } from '../setup-repository'
14
+ import { resolvePlacement } from '../ws-bridge'
15
+ import { CheckoutFilePlane, CheckoutRequiresGitError } from './box/checkout-plane'
16
+ import { createRnxCommand } from './box/rnx-command'
17
+
18
+ interface BoxCommandOptions {
19
+ port?: number
20
+ verbose?: boolean
21
+ }
22
+
23
+ const CLOUD_SUBCOMMANDS = new Set(['create', 'ls', 'connect', 'stop', 'delete'])
24
+
25
+ function usage(): void {
26
+ const rnx = rnxPublicBrand.commandName
27
+ console.log(`
28
+ ${rnx} box — start a local box and enter its shell
29
+
30
+ usage:
31
+ ${rnx} box start a local box over the current checkout
32
+ ${rnx} box create create a cloud box and enter its shell
33
+ ${rnx} box create --name=<n> create a named cloud box
34
+
35
+ a local box mounts this checkout at ${PROJECT_MOUNT}, gives you a shell with
36
+ ls, cat, find, rg, sed, pipes and redirects, and registers ${rnx} itself as a
37
+ shell command. writes go straight to the checkout — there is no second copy.
38
+ `)
39
+ }
40
+
41
+ export async function runBox(
42
+ args: string[],
43
+ opts: BoxCommandOptions = {},
44
+ ): Promise<number> {
45
+ if (args.includes('--help') || args.includes('-h')) {
46
+ usage()
47
+ return 0
48
+ }
49
+
50
+ // a box inside a box would mount the same checkout twice and give the
51
+ // user two shells writing through one filesystem. refuse at the top rather
52
+ // than letting the second one start and quietly race the first.
53
+ const placement = resolvePlacement()
54
+ if (placement === 'local-box') {
55
+ console.error(
56
+ ` ${rnxPublicBrand.commandName} box: you are already in a box.\n` +
57
+ ` this shell is the box — run ${rnxPublicBrand.commandName} commands directly, or \`exit\` to leave.`,
58
+ )
59
+ return 1
60
+ }
61
+
62
+ const subcommand = args.find((arg) => !arg.startsWith('-'))
63
+ if (subcommand !== undefined) {
64
+ if (CLOUD_SUBCOMMANDS.has(subcommand)) {
65
+ console.error(
66
+ ` ${rnxPublicBrand.commandName} box ${subcommand} needs a cloud box, which is not built yet.\n` +
67
+ ` \`${rnxPublicBrand.commandName} box\` runs the same shell locally over this checkout.`,
68
+ )
69
+ return 1
70
+ }
71
+ console.error(
72
+ ` unknown command: ${rnxPublicBrand.commandName} box ${subcommand}\n` +
73
+ ` run \`${rnxPublicBrand.commandName} box --help\` to see the box surface.`,
74
+ )
75
+ return 1
76
+ }
77
+
78
+ return runLocalBox(opts)
79
+ }
80
+
81
+ async function runLocalBox(opts: BoxCommandOptions): Promise<number> {
82
+ // findRepositoryPackageRoot answers both halves of what a box needs: the
83
+ // nearest package.json, and only when it sits inside a git repository. git
84
+ // is what decides which files belong to the project, so a box without it
85
+ // would have to walk the whole directory tree — measured at 230k files on
86
+ // this repo against 15k from git.
87
+ const root = findRepositoryPackageRoot(process.cwd())
88
+ if (!root) {
89
+ console.error(
90
+ ` ${rnxPublicBrand.commandName} box: no project here.\n` +
91
+ ` the box needs a directory inside a git repository that has a\n` +
92
+ ` package.json. it asks git which files belong to the project, so it\n` +
93
+ ` never walks node_modules or build output. run \`git init\` if this is\n` +
94
+ ` a new project.`,
95
+ )
96
+ return 1
97
+ }
98
+
99
+ const plane = new CheckoutFilePlane(root)
100
+ try {
101
+ await plane.refresh()
102
+ } catch (error) {
103
+ if (error instanceof CheckoutRequiresGitError) {
104
+ console.error(` ${rnxPublicBrand.commandName} box: ${error.message}`)
105
+ return 1
106
+ }
107
+ throw error
108
+ }
109
+
110
+ const shell = createHarnessShell({
111
+ projectFs: new HarnessProjectFs(plane),
112
+ commands: [createRnxCommand({ root, shellCwd: () => shell.cwd })],
113
+ })
114
+
115
+ const relative = path.relative(root, process.cwd())
116
+ console.log(` ...starting local box`)
117
+ console.log(` ...mounting ${root} at ${PROJECT_MOUNT}`)
118
+ console.log(` ...accessing shell\n`)
119
+ if (opts.verbose) {
120
+ console.log(` ${plane.index().size} project files indexed\n`)
121
+ }
122
+
123
+ return repl(shell, plane, relative)
124
+ }
125
+
126
+ async function repl(
127
+ shell: ReturnType<typeof createHarnessShell>,
128
+ plane: CheckoutFilePlane,
129
+ startRelative: string,
130
+ ): Promise<number> {
131
+ const rl = createInterface({ input: process.stdin, output: process.stdout })
132
+
133
+ // enter where the user was standing, so `rnx box` from a subdirectory does
134
+ // not silently teleport them to the project root.
135
+ if (startRelative && !startRelative.startsWith('..')) {
136
+ await shell.exec(`cd ${JSON.stringify(startRelative)}`)
137
+ }
138
+
139
+ const prompt = () => {
140
+ rl.setPrompt(`${shell.cwd} > `)
141
+ rl.prompt()
142
+ }
143
+
144
+ // readline delivers a pasted block, or a piped script, as a burst of `line`
145
+ // events. run them one at a time in order — dropping the ones that arrive
146
+ // while a command is running would silently execute only the first line of
147
+ // a paste.
148
+ let queue: Promise<void> = Promise.resolve()
149
+ // stdin ended: stop printing prompts, but still finish what is already
150
+ // queued — piping a script in must run every line, not just the first.
151
+ let inputEnded = false
152
+ // the user typed `exit`: abandon anything still queued behind it.
153
+ let stopped = false
154
+
155
+ const run = async (command: string) => {
156
+ if (stopped) return
157
+ try {
158
+ // the checkout is shared with editors, agents, and the user's own
159
+ // terminal, so re-read the file set before every command rather than
160
+ // trusting a snapshot taken when the box started.
161
+ await plane.refresh()
162
+ const result = await shell.exec(command)
163
+ if (result.text && result.text !== '(no output)') console.log(result.text)
164
+ } catch (error) {
165
+ console.error(error instanceof Error ? error.message : String(error))
166
+ }
167
+ if (!inputEnded && !stopped) prompt()
168
+ }
169
+
170
+ return new Promise<number>((resolve) => {
171
+ prompt()
172
+
173
+ rl.on('line', (line) => {
174
+ const command = line.trim()
175
+ if (!command) {
176
+ queue = queue.then(() => {
177
+ if (!inputEnded && !stopped) prompt()
178
+ })
179
+ return
180
+ }
181
+ if (command === 'exit' || command === 'quit') {
182
+ queue = queue.then(() => {
183
+ stopped = true
184
+ rl.close()
185
+ })
186
+ return
187
+ }
188
+ queue = queue.then(() => run(command))
189
+ })
190
+
191
+ rl.on('close', () => {
192
+ inputEnded = true
193
+ // let any already-queued command finish before the process exits.
194
+ queue.then(() => {
195
+ console.log()
196
+ resolve(0)
197
+ })
198
+ })
199
+ })
200
+ }
@@ -9,6 +9,7 @@ import {
9
9
  } from 'sootsim-engine/settings'
10
10
  import { WebSocket } from 'ws'
11
11
  import { probePort } from '../../scripts/dev-server-scanner'
12
+ import { isAppDeepLinkTarget, openAppUrl } from '../../src/app-url'
12
13
  import { isLoopbackHost } from '../../src/backend-origin'
13
14
  import { resolveSootsimShellBaseUrlForBridgePort } from '../../src/bridge-constants'
14
15
  import {
@@ -65,7 +66,7 @@ import {
65
66
  worldForBridgePort,
66
67
  type BridgeSimInfo,
67
68
  } from '../ws-bridge'
68
- import { WAIT_READY_PROBE, readyProbeHasContent, type ReadyProbe } from './inspect/core'
69
+ import { inspectDescribe, inspectWaitReady } from './inspect/core'
69
70
  import { isAgentEnv } from './inspect/env'
70
71
  import type { DriverId, DriverLaunchResult } from '../drivers/types'
71
72
 
@@ -161,31 +162,6 @@ function normalizeKnownTarget(target: string): string {
161
162
  return target.trim()
162
163
  }
163
164
 
164
- const DEEP_LINK_SCHEME_RE = /^[A-Za-z][A-Za-z0-9+.-]*:/
165
-
166
- export function normalizeDeepLinkTarget(target: string): string {
167
- const trimmed = target.trim()
168
- if (!trimmed) {
169
- throw new Error('expected <url-or-route>')
170
- }
171
- if (DEEP_LINK_SCHEME_RE.test(trimmed) || trimmed.startsWith('/')) {
172
- return trimmed
173
- }
174
- return `/${trimmed}`
175
- }
176
-
177
- function isObjectRecord(value: unknown): value is Record<string, unknown> {
178
- return value !== null && typeof value === 'object'
179
- }
180
-
181
- function shouldOpenAsDeepLink(target: string): boolean {
182
- const trimmed = target.trim()
183
- if (!trimmed) return false
184
- if (trimmed.startsWith('/')) return true
185
- if (!DEEP_LINK_SCHEME_RE.test(trimmed)) return false
186
- return !/^https?:\/\//i.test(trimmed)
187
- }
188
-
189
165
  export async function ensureBridgeForOpen(
190
166
  preferredPort: number,
191
167
  explicitPort = false,
@@ -1272,19 +1248,12 @@ async function maybeDescribeAfterOpen(wsPort: number, simId: string, args: strin
1272
1248
  simIdSource: 'flag',
1273
1249
  })
1274
1250
  try {
1275
- let readyProbe: ReadyProbe | null = null
1251
+ let readyProbe: Awaited<ReturnType<typeof inspectWaitReady>> | null = null
1276
1252
  try {
1277
- readyProbe = (await bridge.send({
1278
- type: 'evaluate',
1279
- code: WAIT_READY_PROBE,
1280
- })) as ReadyProbe | null
1253
+ readyProbe = await inspectWaitReady(bridge, 1)
1281
1254
  } catch {}
1282
1255
  if (readyProbe) {
1283
- const readyEnough =
1284
- readyProbe.flag === true &&
1285
- !readyProbe.loadingText &&
1286
- readyProbeHasContent(readyProbe)
1287
- if (!readyEnough) {
1256
+ if (!readyProbe.ready) {
1288
1257
  const reason = readyProbe.loadingText
1289
1258
  ? `still showing "${readyProbe.loadingText}"`
1290
1259
  : readyProbe.flag !== true
@@ -1298,29 +1267,16 @@ async function maybeDescribeAfterOpen(wsPort: number, simId: string, args: strin
1298
1267
  console.log(' before interacting, run: rnx wait ready --max-ms 120000')
1299
1268
  }
1300
1269
  }
1301
- const dumpOpts = { describe: true, verbose: false, filter: '' }
1302
- const code = `(async () => {
1303
- const t = window.__sootsimTest
1304
- const ms = window.SootSim?.bridges?.mainShell
1305
- if (!t) return null
1306
- let shell = null
1307
- try { shell = ms?.getState ? await ms.getState() : null } catch {}
1308
- const tree = await t.dumpTree(12, ${JSON.stringify(dumpOpts)})
1309
- return { tree, shell }
1310
- })()`
1311
- const result = (await bridge.send({ type: 'evaluate', code })) as {
1312
- tree: string | null
1313
- shell: { state?: string; activeApp?: string } | null
1314
- } | null
1270
+ const result = await inspectDescribe(bridge, {
1271
+ describe: true,
1272
+ verbose: false,
1273
+ filter: '',
1274
+ compact: true,
1275
+ hideXy: false,
1276
+ maxDepth: 12,
1277
+ })
1315
1278
  if (!result?.tree) return
1316
1279
  console.log('')
1317
- if (result.shell?.state) {
1318
- const parts = [
1319
- `state=${result.shell.state}`,
1320
- result.shell.activeApp ? `app=${result.shell.activeApp}` : null,
1321
- ].filter(Boolean)
1322
- console.log(` shell: ${parts.join(' ')}`)
1323
- }
1324
1280
  console.log(result.tree)
1325
1281
  } finally {
1326
1282
  bridge.close()
@@ -1385,27 +1341,10 @@ export async function runLinkCommand(args: string[], opts: ControlCommandOptions
1385
1341
  process.exit(1)
1386
1342
  }
1387
1343
 
1388
- let target: string
1389
- try {
1390
- target = normalizeDeepLinkTarget(parsed.positional[0] ?? '')
1391
- } catch (err) {
1392
- console.error(` rnx open: ${err instanceof Error ? err.message : String(err)}`)
1393
- process.exit(1)
1394
- }
1395
-
1396
1344
  const bridge = createBridgeFromParsed(parsed)
1397
1345
  const simHint = parsed.simId ? ` --sim ${parsed.simId}` : ''
1398
1346
  try {
1399
- const result = await bridge.send({
1400
- type: 'call',
1401
- path: 'SootSim.bridges.test.openDeepLink',
1402
- args: [target],
1403
- })
1404
- if (isObjectRecord(result) && result.ok === false) {
1405
- throw new Error(
1406
- typeof result.error === 'string' ? result.error : 'deep-link dispatch failed',
1407
- )
1408
- }
1347
+ const target = await openAppUrl(bridge, parsed.positional[0] ?? '')
1409
1348
  console.log(` opened deep link: ${target}`)
1410
1349
  } catch (err) {
1411
1350
  console.error(` link failed: ${err instanceof Error ? err.message : String(err)}`)
@@ -1475,7 +1414,7 @@ export async function runOpenCommand(
1475
1414
  }
1476
1415
  const target = parsed.positional[0] || ''
1477
1416
 
1478
- if (shouldOpenAsDeepLink(target)) {
1417
+ if (isAppDeepLinkTarget(target)) {
1479
1418
  if (openInNewSim) {
1480
1419
  console.error(
1481
1420
  ' rnx open: deep links target the current app; remove --new, --profile, or --ephemeral',
@@ -12,6 +12,7 @@
12
12
 
13
13
  import { spawn } from 'child_process'
14
14
  import { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync } from 'fs'
15
+ import { createRequire } from 'module'
15
16
  import { tmpdir } from 'os'
16
17
  import { dirname, resolve, join, relative } from 'path'
17
18
  import { rnxPublicBrand } from '../../src/public-brand'
@@ -195,7 +196,23 @@ export async function runDetox(args: string[], opts: RunDetoxOpts = {}): Promise
195
196
  console.log(` port: ${port}${headed ? ' (headed)' : ''}`)
196
197
  if (localConfig) console.log(` config: ${localConfig}`)
197
198
 
198
- const child = spawn('npx', jestArgs, {
199
+ // proof capture retains several full-resolution frames while jest tears its
200
+ // browser down. node 24.16.0's maglev collector can crash in
201
+ // ClearStaleLeftTrimmedPointerVisitor after every assertion has passed.
202
+ // the conformance lane pins that exact node runtime, so disable only its
203
+ // unstable optimization tier. normal user detox runs keep their own node
204
+ // defaults and still resolve jest through npx.
205
+ const proofCapture =
206
+ process.env.CONFORMANCE_PROOF === '1' || !!process.env.CONFORMANCE_PROOF_DIR
207
+ const command = proofCapture ? 'node' : 'npx'
208
+ const commandArgs = proofCapture
209
+ ? [
210
+ '--no-maglev',
211
+ createRequire(join(process.cwd(), 'package.json')).resolve('jest/bin/jest'),
212
+ ...jestArgs.slice(1),
213
+ ]
214
+ : jestArgs
215
+ const child = spawn(command, commandArgs, {
199
216
  cwd: process.cwd(),
200
217
  stdio: 'inherit',
201
218
  env: {