rnxsim 0.1.369 → 0.1.370

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 (253) hide show
  1. package/cli/auth.ts +2 -1
  2. package/cli/bin.ts +283 -273
  3. package/cli/commands/app-fonts.ts +3 -2
  4. package/cli/commands/assert.ts +8 -7
  5. package/cli/commands/auth.ts +2 -1
  6. package/cli/commands/box/checkout-plane.ts +45 -16
  7. package/cli/commands/config.ts +2 -1
  8. package/cli/commands/control.ts +37 -33
  9. package/cli/commands/daemon.ts +22 -21
  10. package/cli/commands/debug.ts +17 -15
  11. package/cli/commands/detox.ts +5 -4
  12. package/cli/commands/device.ts +6 -5
  13. package/cli/commands/electron.ts +6 -5
  14. package/cli/commands/flow.ts +12 -11
  15. package/cli/commands/inspect/find.ts +2 -1
  16. package/cli/commands/inspect/keyboard.ts +2 -1
  17. package/cli/commands/inspect/sleep.ts +2 -1
  18. package/cli/commands/inspect/wait-event.ts +4 -3
  19. package/cli/commands/inspect/wait-idle.ts +2 -1
  20. package/cli/commands/inspect/wait-ready.ts +3 -2
  21. package/cli/commands/inspect/wait-selector.ts +4 -3
  22. package/cli/commands/inspect/wait.ts +2 -1
  23. package/cli/commands/inspect.ts +82 -80
  24. package/cli/commands/install-desktop.ts +5 -4
  25. package/cli/commands/login.ts +3 -2
  26. package/cli/commands/mode.ts +2 -1
  27. package/cli/commands/record.ts +38 -37
  28. package/cli/commands/runtime.ts +9 -8
  29. package/cli/commands/screenshot-layers.ts +4 -3
  30. package/cli/commands/screenshot-mode.ts +2 -1
  31. package/cli/commands/screenshot.ts +8 -7
  32. package/cli/commands/screenshots-capture.ts +2 -1
  33. package/cli/commands/serve.ts +5 -4
  34. package/cli/commands/shell-boolean-mode.ts +2 -1
  35. package/cli/commands/skills.ts +11 -10
  36. package/cli/commands/telemetry.ts +2 -1
  37. package/cli/commands/three-mode.ts +2 -1
  38. package/cli/commands/timeline.ts +4 -3
  39. package/cli/commands/upload.ts +14 -13
  40. package/cli/commands/what-happened.ts +2 -1
  41. package/cli/recording-access.ts +6 -5
  42. package/cli/run-rnx.ts +140 -0
  43. package/dist-cli/bin.js +6 -6
  44. package/dist-cli/chunks/{agent-OUUFWDLF.js → agent-3R4424LW.js} +2 -2
  45. package/dist-cli/chunks/{agent-wrapper-7BE7DR4R.js → agent-wrapper-4PPWZWSY.js} +2 -2
  46. package/dist-cli/chunks/app-fonts-KYXB6CXQ.js +4 -0
  47. package/dist-cli/chunks/{assert-I3CMRNS7.js → assert-7EZOWGYQ.js} +11 -11
  48. package/dist-cli/chunks/auth-L7M36H6C.js +12 -0
  49. package/dist-cli/chunks/{beta-BHSKY4K4.js → beta-FKAF6YWJ.js} +2 -2
  50. package/dist-cli/chunks/{box-3CVWXVTT.js → box-5Y5VBMHV.js} +342 -342
  51. package/dist-cli/chunks/{camera-MNK73TYV.js → camera-XHF2RPFG.js} +2 -2
  52. package/dist-cli/chunks/{chunk-NPVEBXU3.js → chunk-2FOGXVOK.js} +2 -2
  53. package/dist-cli/chunks/{chunk-UDXQCEJU.js → chunk-3BG6UCQY.js} +2 -2
  54. package/dist-cli/chunks/{chunk-V7I4CTAX.js → chunk-3SOPYG7S.js} +2 -2
  55. package/dist-cli/chunks/{chunk-QE5MC4V3.js → chunk-3UXXYJVD.js} +1 -1
  56. package/dist-cli/chunks/{chunk-EKAEEWC2.js → chunk-5BR33NAZ.js} +1 -1
  57. package/dist-cli/chunks/{chunk-FGGESBWD.js → chunk-5RKRVS5A.js} +2 -2
  58. package/dist-cli/chunks/{chunk-MXWME7ET.js → chunk-64JAEJ6N.js} +1 -1
  59. package/dist-cli/chunks/{chunk-AKJYEX54.js → chunk-6A6L3VJR.js} +2 -2
  60. package/dist-cli/chunks/chunk-6GPS7U4X.js +74 -0
  61. package/dist-cli/chunks/{chunk-OROUEG22.js → chunk-6S5LJV2O.js} +2 -2
  62. package/dist-cli/chunks/{chunk-ZW6JXEVX.js → chunk-6W4HIWC7.js} +2 -2
  63. package/dist-cli/chunks/{chunk-6BMM6OCW.js → chunk-72S5FCBY.js} +2 -2
  64. package/dist-cli/chunks/{chunk-TBK5UXW2.js → chunk-7C7TJYZ3.js} +2 -2
  65. package/dist-cli/chunks/{chunk-D6MGELKZ.js → chunk-AALKIAUZ.js} +1 -1
  66. package/dist-cli/chunks/{chunk-WUR4FXWT.js → chunk-ARMXC3C3.js} +25 -25
  67. package/dist-cli/chunks/chunk-AVJBT5LF.js +4 -0
  68. package/dist-cli/chunks/chunk-BMIJZNKD.js +28 -0
  69. package/dist-cli/chunks/{chunk-N5KDDGV6.js → chunk-C7BA5CCM.js} +2 -2
  70. package/dist-cli/chunks/{chunk-F4OTMARL.js → chunk-CMVRTZ5G.js} +2 -2
  71. package/dist-cli/chunks/chunk-D24EOJ4C.js +24 -0
  72. package/dist-cli/chunks/{chunk-A56GQVNF.js → chunk-D2GZASRV.js} +1 -1
  73. package/dist-cli/chunks/{chunk-IYPMULI7.js → chunk-DHYTRCIP.js} +2 -2
  74. package/dist-cli/chunks/{chunk-X3PEDNLV.js → chunk-DMHSD6YJ.js} +2 -2
  75. package/dist-cli/chunks/{chunk-YPBDPVLR.js → chunk-DUXHYPBP.js} +2 -2
  76. package/dist-cli/chunks/{chunk-4SCVGBOX.js → chunk-DXV37CJN.js} +1 -1
  77. package/dist-cli/chunks/{chunk-H2GB2PLY.js → chunk-EOIBLKZO.js} +2 -2
  78. package/dist-cli/chunks/{chunk-UQJY6O72.js → chunk-EQ3OE2AV.js} +1 -1
  79. package/dist-cli/chunks/{chunk-3454VMHD.js → chunk-EWSWOWXK.js} +2 -2
  80. package/dist-cli/chunks/{chunk-FHRG772G.js → chunk-FA75F34Z.js} +1 -1
  81. package/dist-cli/chunks/{chunk-LU3AHE6T.js → chunk-FK4JSTZ3.js} +2 -2
  82. package/dist-cli/chunks/{chunk-AJJCYLBC.js → chunk-GJRLPJQL.js} +2 -2
  83. package/dist-cli/chunks/{chunk-WGE2P25F.js → chunk-GRO74J2X.js} +1 -1
  84. package/dist-cli/chunks/chunk-H5X5FJ63.js +99 -0
  85. package/dist-cli/chunks/chunk-HEKHLQ3B.js +497 -0
  86. package/dist-cli/chunks/chunk-HQQTS2CB.js +30 -0
  87. package/dist-cli/chunks/{chunk-DKSZZABN.js → chunk-I52NT6VG.js} +1 -1
  88. package/dist-cli/chunks/{chunk-A72MIIJI.js → chunk-IDLFVRPN.js} +1 -1
  89. package/dist-cli/chunks/{chunk-6F3WOWFC.js → chunk-JZLDHPB7.js} +2 -2
  90. package/dist-cli/chunks/{chunk-ZNNE5MAV.js → chunk-LIMWG54N.js} +1 -1
  91. package/dist-cli/chunks/{chunk-TTUZ5MCK.js → chunk-LUOHRBJG.js} +1 -1
  92. package/dist-cli/chunks/{chunk-B5NW4TT3.js → chunk-LXPHL6FO.js} +2 -2
  93. package/dist-cli/chunks/{chunk-TAC4CEIX.js → chunk-MDCY532E.js} +2 -2
  94. package/dist-cli/chunks/{chunk-45X3D6CI.js → chunk-NJMU5BN6.js} +1 -1
  95. package/dist-cli/chunks/{chunk-2APXX4RN.js → chunk-NR2V2ONN.js} +2 -2
  96. package/dist-cli/chunks/{chunk-JH5KE2OI.js → chunk-NTC46LSA.js} +2 -2
  97. package/dist-cli/chunks/{chunk-RV76U3EI.js → chunk-O6QLKSAM.js} +10 -10
  98. package/dist-cli/chunks/{chunk-ZEF57HQO.js → chunk-OJZHECGH.js} +1 -1
  99. package/dist-cli/chunks/chunk-OTFTMGAL.js +7 -0
  100. package/dist-cli/chunks/{chunk-RYGQ6LZN.js → chunk-PAUEZO6F.js} +2 -2
  101. package/dist-cli/chunks/{chunk-CCI3DZKE.js → chunk-PIZXOXBL.js} +2 -2
  102. package/dist-cli/chunks/{chunk-JLOOLA47.js → chunk-PNOENVMJ.js} +1 -1
  103. package/dist-cli/chunks/{chunk-YLNSLMQM.js → chunk-Q4MRJ6PO.js} +2 -2
  104. package/dist-cli/chunks/{chunk-5QFJFASW.js → chunk-RRX52O37.js} +1 -1
  105. package/dist-cli/chunks/{chunk-FBMNYSSW.js → chunk-U63T6LIQ.js} +1 -1
  106. package/dist-cli/chunks/{chunk-2PVVIPQS.js → chunk-UFVAAWGZ.js} +1 -1
  107. package/dist-cli/chunks/{chunk-CZ2HCNRK.js → chunk-VVQCZ42C.js} +1 -1
  108. package/dist-cli/chunks/{chunk-USHV3QFZ.js → chunk-WFQHT4QD.js} +2 -2
  109. package/dist-cli/chunks/{chunk-DCPDMF27.js → chunk-WPV4H5S4.js} +2 -2
  110. package/dist-cli/chunks/{chunk-SSBZA5VZ.js → chunk-X6WQ4YWE.js} +1 -1
  111. package/dist-cli/chunks/{chunk-VGJLCFSC.js → chunk-YIZGNT7A.js} +1 -1
  112. package/dist-cli/chunks/{chunk-JXARZBWS.js → chunk-Z2VBSNFI.js} +1 -1
  113. package/dist-cli/chunks/{chunk-4G4VCT2M.js → chunk-Z3DENGXW.js} +2 -2
  114. package/dist-cli/chunks/{cleanup-H4XFRMZV.js → cleanup-MXMMJ7JW.js} +2 -2
  115. package/dist-cli/chunks/cli-version-MLS55UTY.js +4 -0
  116. package/dist-cli/chunks/{compat-NKGYERLN.js → compat-E6KE5KDI.js} +2 -2
  117. package/dist-cli/chunks/config-QSLLVZDS.js +13 -0
  118. package/dist-cli/chunks/{connect-WWWAVVCF.js → connect-QE4VU6GB.js} +2 -2
  119. package/dist-cli/chunks/control-XD5F6K5T.js +4 -0
  120. package/dist-cli/chunks/daemon-UN5SE2JO.js +4 -0
  121. package/dist-cli/chunks/{debug-RO6GLDIM.js → debug-5FG3MBJS.js} +20 -20
  122. package/dist-cli/chunks/desktop-5EZX7NX5.js +31 -0
  123. package/dist-cli/chunks/detox-WJGQP5TM.js +52 -0
  124. package/dist-cli/chunks/device-D7LKCAMS.js +18 -0
  125. package/dist-cli/chunks/{diagnose-HDRTUPDO.js → diagnose-A3W6ZTGA.js} +2 -2
  126. package/dist-cli/chunks/{disk-cleanup-QP2NYN43.js → disk-cleanup-BDRHX5ZG.js} +2 -2
  127. package/dist-cli/chunks/{do-chain-6LP6SV5J.js → do-chain-RUJNRPPF.js} +2 -2
  128. package/dist-cli/chunks/drivers-3NJ5RL6X.js +4 -0
  129. package/dist-cli/chunks/{env-4XD5D2G6.js → env-U67SQVTV.js} +2 -2
  130. package/dist-cli/chunks/{film-I5SIXLS5.js → film-XC6UAQ3V.js} +3 -3
  131. package/dist-cli/chunks/flow-FHZZD3P6.js +4 -0
  132. package/dist-cli/chunks/help-BYYDUSXF.js +4 -0
  133. package/dist-cli/chunks/{hidden-runtime-alias-LJV2S4M2.js → hidden-runtime-alias-7S2TNZXG.js} +2 -2
  134. package/dist-cli/chunks/{home-paths-RFI2QRC3.js → home-paths-LMO5LB4F.js} +2 -2
  135. package/dist-cli/chunks/inspect-RQCTWDCC.js +4 -0
  136. package/dist-cli/chunks/install-desktop-GOWXCQUE.js +4 -0
  137. package/dist-cli/chunks/login-JIU42S4C.js +26 -0
  138. package/dist-cli/chunks/{logout-DKXZZ6HU.js → logout-JTAWQXZO.js} +2 -2
  139. package/dist-cli/chunks/{maestro-WKGVVWRA.js → maestro-TYBQYOGB.js} +3 -3
  140. package/dist-cli/chunks/{maestro-generate-KCK6RK2C.js → maestro-generate-YLAGWVKE.js} +3 -3
  141. package/dist-cli/chunks/mode-DV24ZRZ6.js +26 -0
  142. package/dist-cli/chunks/{optional-demo-registry-EUSKBIPP.js → optional-demo-registry-JZH66VIM.js} +2 -2
  143. package/dist-cli/chunks/{perf-IMZMA5MT.js → perf-LXXJ4EAX.js} +2 -2
  144. package/dist-cli/chunks/{permissions-SLWXTLH6.js → permissions-KAOVZUPW.js} +2 -2
  145. package/dist-cli/chunks/{platform-HQ735LAE.js → platform-KW35446W.js} +3 -3
  146. package/dist-cli/chunks/privacy-MIFE72CG.js +4 -0
  147. package/dist-cli/chunks/record-EK3ETFHZ.js +72 -0
  148. package/dist-cli/chunks/report-issue-NW5PCAZF.js +29 -0
  149. package/dist-cli/chunks/reset-RZ4O5AFJ.js +4 -0
  150. package/dist-cli/chunks/resolve-target-7R5BNYMM.js +4 -0
  151. package/dist-cli/chunks/runtime-MZHLSRS6.js +4 -0
  152. package/dist-cli/chunks/{screenshot-command-HAJB5FHK.js → screenshot-command-TQSPTFT2.js} +11 -11
  153. package/dist-cli/chunks/screenshot-layers-IZP3L3AQ.js +20 -0
  154. package/dist-cli/chunks/screenshots-capture-K7LJOVAE.js +42 -0
  155. package/dist-cli/chunks/{serve-TM46TKFW.js → serve-MMX4WXXG.js} +18 -18
  156. package/dist-cli/chunks/setup-YFP3KEQZ.js +4 -0
  157. package/dist-cli/chunks/{sim-client-LQUZSO45.js → sim-client-KLAYIFOC.js} +2 -2
  158. package/dist-cli/chunks/skills-N4D3POIS.js +50 -0
  159. package/dist-cli/chunks/{state-RUVM4Z4S.js → state-ES6TFQZF.js} +2 -2
  160. package/dist-cli/chunks/{storage-GQ33UTYN.js → storage-52DR5V2C.js} +2 -2
  161. package/dist-cli/chunks/store-HOL45WCI.js +4 -0
  162. package/dist-cli/chunks/telemetry-BV6OTYPF.js +4 -0
  163. package/dist-cli/chunks/timeline-NRVNB5JR.js +24 -0
  164. package/dist-cli/chunks/{upgrade-AZDZLUIT.js → upgrade-LNED5YQ2.js} +2 -2
  165. package/dist-cli/chunks/upload-RXKHFIBU.js +4 -0
  166. package/dist-cli/chunks/{version-N3ALB75G.js → version-VEF2DYGG.js} +2 -2
  167. package/dist-cli/chunks/{web-XPHFZATA.js → web-CGDE6S73.js} +2 -2
  168. package/dist-cli/chunks/welcome-FT2AMUWW.js +5 -0
  169. package/dist-cli/chunks/what-happened-FSIDWL32.js +25 -0
  170. package/dist-cli/chunks/{ws-bridge-RUXOYOU6.js → ws-bridge-QXFQLRE7.js} +2 -2
  171. package/dist-lib/agent-daemon-client.cjs +1 -1
  172. package/dist-lib/agent-events.cjs +1 -1
  173. package/dist-lib/agent-identity.cjs +1 -1
  174. package/dist-lib/agent-sessions.cjs +1 -1
  175. package/dist-lib/attached-projects.cjs +1 -1
  176. package/dist-lib/auth/shared-session.cjs +1 -1
  177. package/dist-lib/backend-origin.cjs +1 -1
  178. package/dist-lib/beta.cjs +1 -1
  179. package/dist-lib/beta.mjs +1 -1
  180. package/dist-lib/bridge-constants.cjs +1 -1
  181. package/dist-lib/bridge-contract-input.cjs +1 -1
  182. package/dist-lib/bridge-contract-input.mjs +1 -1
  183. package/dist-lib/bridge-contract.cjs +1 -1
  184. package/dist-lib/bridge-contract.mjs +1 -1
  185. package/dist-lib/cli-constants.cjs +1 -1
  186. package/dist-lib/config.cjs +1 -1
  187. package/dist-lib/detox/index.cjs +1 -1
  188. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  189. package/dist-lib/home-paths.cjs +1 -1
  190. package/dist-lib/host/bridge-host.cjs +1 -1
  191. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  192. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  193. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  194. package/dist-lib/host/replacement-module-handler.cjs +1 -1
  195. package/dist-lib/host/websocket-proxy.cjs +1 -1
  196. package/dist-lib/index.cjs +1 -1
  197. package/dist-lib/jump-to-source-babel.cjs +1 -1
  198. package/dist-lib/menu.cjs +1 -1
  199. package/dist-lib/menu.mjs +1 -1
  200. package/dist-lib/metro-production-bundle.cjs +1 -1
  201. package/dist-lib/metro-production-bundle.mjs +1 -1
  202. package/dist-lib/metro.cjs +1 -1
  203. package/dist-lib/profiles.cjs +1 -1
  204. package/dist-lib/public-brand.cjs +1 -1
  205. package/dist-lib/react-native-host-modules.cjs +2 -1
  206. package/dist-lib/react-native-host-modules.mjs +2 -1
  207. package/dist-lib/render-mode.cjs +1 -1
  208. package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
  209. package/dist-lib/sdk.cjs +1 -1
  210. package/dist-lib/sdk.mjs +1 -1
  211. package/dist-lib/skills.cjs +79 -47
  212. package/dist-lib/vite.cjs +1 -1
  213. package/package.json +1 -1
  214. package/src/react-native-host-modules.ts +1 -0
  215. package/dist-cli/chunks/app-fonts-66V7TL26.js +0 -4
  216. package/dist-cli/chunks/auth-3FMWUVJH.js +0 -12
  217. package/dist-cli/chunks/chunk-3NLB5LCN.js +0 -28
  218. package/dist-cli/chunks/chunk-5RWUFIZW.js +0 -24
  219. package/dist-cli/chunks/chunk-COXCR6UU.js +0 -7
  220. package/dist-cli/chunks/chunk-ROISJKH3.js +0 -99
  221. package/dist-cli/chunks/chunk-SUHAD6N2.js +0 -30
  222. package/dist-cli/chunks/chunk-TIB3CTRX.js +0 -497
  223. package/dist-cli/chunks/chunk-YFZLDFZK.js +0 -74
  224. package/dist-cli/chunks/cli-version-MJWLDXBW.js +0 -4
  225. package/dist-cli/chunks/config-WK7467KI.js +0 -13
  226. package/dist-cli/chunks/control-ESI7IZNN.js +0 -4
  227. package/dist-cli/chunks/daemon-UCLO27YP.js +0 -4
  228. package/dist-cli/chunks/desktop-FF34Z5X4.js +0 -31
  229. package/dist-cli/chunks/detox-Q3CWY5UX.js +0 -52
  230. package/dist-cli/chunks/device-WN4ZWLES.js +0 -18
  231. package/dist-cli/chunks/drivers-OR2JPAXD.js +0 -4
  232. package/dist-cli/chunks/flow-4GWW5DYT.js +0 -4
  233. package/dist-cli/chunks/help-MIIWOT3C.js +0 -4
  234. package/dist-cli/chunks/inspect-3ZGNJENJ.js +0 -4
  235. package/dist-cli/chunks/install-desktop-BYOJVTLS.js +0 -4
  236. package/dist-cli/chunks/login-GNIFSAFE.js +0 -26
  237. package/dist-cli/chunks/mode-YVMX7OJY.js +0 -26
  238. package/dist-cli/chunks/privacy-EDC7PF7G.js +0 -4
  239. package/dist-cli/chunks/record-N4KTTVDI.js +0 -72
  240. package/dist-cli/chunks/report-issue-PP752HAD.js +0 -29
  241. package/dist-cli/chunks/reset-DRMKGVP4.js +0 -4
  242. package/dist-cli/chunks/resolve-target-7WWI32R3.js +0 -4
  243. package/dist-cli/chunks/runtime-EKJV2ZVI.js +0 -4
  244. package/dist-cli/chunks/screenshot-layers-GOCJVM2G.js +0 -20
  245. package/dist-cli/chunks/screenshots-capture-YYJLYA3V.js +0 -42
  246. package/dist-cli/chunks/setup-FVPVIYE4.js +0 -4
  247. package/dist-cli/chunks/skills-V5TE57FN.js +0 -50
  248. package/dist-cli/chunks/store-IKNBDDKM.js +0 -4
  249. package/dist-cli/chunks/telemetry-2ZVU6WLK.js +0 -4
  250. package/dist-cli/chunks/timeline-LP7DMJYT.js +0 -24
  251. package/dist-cli/chunks/upload-DFQK2Q7X.js +0 -4
  252. package/dist-cli/chunks/welcome-2NFYLWLJ.js +0 -5
  253. package/dist-cli/chunks/what-happened-EJ5PHKT7.js +0 -25
@@ -14,6 +14,7 @@
14
14
  // 2 misuse — bad flags, unknown verb (treated as bad by bisect)
15
15
 
16
16
  import { spawn } from 'child_process'
17
+ import { rnxExit } from '../run-rnx'
17
18
 
18
19
  // READ_VERBS lists every command whose stdout we know is valid JSON under
19
20
  // --json. keeping this explicit means `sootsim assert tap 100 200` fails
@@ -403,13 +404,13 @@ function runJq(expr: string, payload: unknown): Promise<string> {
403
404
  export async function runAssert(rawArgs: string[]): Promise<void> {
404
405
  if (rawArgs[0] === '--help' || rawArgs[0] === '-h' || rawArgs.length === 0) {
405
406
  printHelp()
406
- process.exit(rawArgs.length === 0 ? 2 : 0)
407
+ rnxExit(rawArgs.length === 0 ? 2 : 0)
407
408
  }
408
409
 
409
410
  const parsed = parseAssertArgs(rawArgs)
410
411
  if ('error' in parsed) {
411
412
  process.stderr.write(` assert: ${parsed.error}\n`)
412
- process.exit(2)
413
+ rnxExit(2)
413
414
  }
414
415
 
415
416
  const { verbArgs, predicates, withinMs, negate, quiet, allowTimeout } = parsed
@@ -420,7 +421,7 @@ export async function runAssert(rawArgs: string[]): Promise<void> {
420
421
  ` assert: unknown or non-read verb: ${verbArgs.join(' ')}\n` +
421
422
  ` supported: ${Array.from(READ_VERBS).sort().join(', ')}\n`,
422
423
  )
423
- process.exit(2)
424
+ rnxExit(2)
424
425
  }
425
426
 
426
427
  // default assertion when none given is --exists. matches the common
@@ -452,7 +453,7 @@ export async function runAssert(rawArgs: string[]): Promise<void> {
452
453
  const label = exitCode === 1 ? 'fail' : 'skip'
453
454
  process.stderr.write(` assert: ${label} — ${lastVerbSkipReason}\n`)
454
455
  }
455
- process.exit(exitCode)
456
+ rnxExit(exitCode)
456
457
  }
457
458
 
458
459
  let allPass = true
@@ -468,7 +469,7 @@ export async function runAssert(rawArgs: string[]): Promise<void> {
468
469
  }
469
470
  } catch (err: any) {
470
471
  if (!quiet) process.stderr.write(` assert: skip — ${err.message}\n`)
471
- process.exit(125)
472
+ rnxExit(125)
472
473
  }
473
474
 
474
475
  const pass = negate ? !allPass : allPass
@@ -478,7 +479,7 @@ export async function runAssert(rawArgs: string[]): Promise<void> {
478
479
  ` assert: pass${attempts > 1 ? ` (attempt ${attempts})` : ''} — ${reasons.join('; ')}\n`,
479
480
  )
480
481
  }
481
- process.exit(0)
482
+ rnxExit(0)
482
483
  }
483
484
 
484
485
  lastFailReason = reasons.join('; ')
@@ -491,7 +492,7 @@ export async function runAssert(rawArgs: string[]): Promise<void> {
491
492
  ` assert: fail${attempts > 1 ? ` after ${attempts} attempt(s)` : ''} — ${lastFailReason}\n`,
492
493
  )
493
494
  }
494
- process.exit(1)
495
+ rnxExit(1)
495
496
  }
496
497
  }
497
498
 
@@ -1,5 +1,6 @@
1
1
  import { refreshSharedDesktopAuthSession } from '../../src/auth/shared-session'
2
2
  import { resolveCliAuth } from '../auth'
3
+ import { rnxExit } from '../run-rnx'
3
4
 
4
5
  function printHelp(): void {
5
6
  console.log(`
@@ -55,5 +56,5 @@ export async function runAuth(args: string[]): Promise<void> {
55
56
 
56
57
  console.error(` unknown auth command: ${sub}`)
57
58
  printHelp()
58
- process.exit(1)
59
+ rnxExit(1)
59
60
  }
@@ -1,7 +1,6 @@
1
- // ProjectFilePlane over a real local checkout — the third provider behind the
2
- // seam Contrast's browser host and its server host already implement. writes
3
- // land on the user's actual disk, so this plane refuses anything it cannot
4
- // represent faithfully rather than round-tripping it through a string.
1
+ // ProjectFilePlane over a real local checkout — the second host behind the
2
+ // seam Contrast's browser already implements. writes land on the user's actual
3
+ // disk, so nothing here converts content: the plane reads and writes bytes.
5
4
  //
6
5
  // the file set comes from `git ls-files -co --exclude-standard`, which is the
7
6
  // project's own definition of what belongs to it. a hardcoded skip list was
@@ -20,14 +19,14 @@ const execFileAsync = promisify(execFile)
20
19
 
21
20
  export class CheckoutRequiresGitError extends Error {}
22
21
 
23
- // a NUL byte is the usual "this is not text" test. the plane serves strings,
24
- // so a binary file cannot survive a read/write round trip. reading one gives
25
- // the same garbage real `cat` gives, which is fine; WRITING one back would
26
- // corrupt a file on the user's actual disk, so binary files are published as
27
- // read-only and the write path refuses them by name. detecting this at read
28
- // time is not an option: just-bash's `cat` discards every readFile error and
29
- // reports "No such file or directory" whatever the error says, so the refusal
30
- // has to live in the index where the filesystem layer can act on it.
22
+ // a NUL byte is the usual "this is not text" test. it is published as metadata
23
+ // rather than acted on here: moving a binary file is byte-exact and perfectly
24
+ // fine, and so is deleting one, but a shell command that produced TEXT cannot
25
+ // be allowed to overwrite it, because that text is a lossy decode of the
26
+ // file's own bytes. HarnessProjectFs owns that refusal. detecting it at write
27
+ // time instead is not an option: just-bash's `cat` discards every readFile
28
+ // error and reports "No such file or directory" whatever the error says, so
29
+ // the fact has to be in the index where the filesystem layer can read it.
31
30
  const BINARY_SNIFF_BYTES = 8192
32
31
 
33
32
  function looksBinary(filePath: string, size: number): boolean {
@@ -45,9 +44,24 @@ function looksBinary(filePath: string, size: number): boolean {
45
44
  }
46
45
  }
47
46
 
47
+ // the box re-indexes before every command, so the sniff above runs against the
48
+ // whole project every time a user presses return. on a 14,889-file checkout
49
+ // that is 277-644 ms of a 430-840 ms refresh, and nearly all of it re-reads
50
+ // files that did not change. a file whose size and mtime both match the last
51
+ // look has the same first 8 KB, so remember the answer against those two.
52
+ // `statSync` already returns mtimeMs, and it carries sub-millisecond
53
+ // resolution here, so the window in which a same-size rewrite could hide is
54
+ // narrower than a millisecond rather than git's one-second racy-clean case.
55
+ interface SniffedFile {
56
+ size: number
57
+ mtimeMs: number
58
+ binary: boolean
59
+ }
60
+
48
61
  export class CheckoutFilePlane implements ProjectFilePlane {
49
62
  readonly root: string
50
63
  private entries = new Map<string, ProjectFileMeta>()
64
+ private sniffed = new Map<string, SniffedFile>()
51
65
 
52
66
  constructor(root: string) {
53
67
  this.root = path.resolve(root)
@@ -77,6 +91,9 @@ export class CheckoutFilePlane implements ProjectFilePlane {
77
91
  }
78
92
 
79
93
  const next = new Map<string, ProjectFileMeta>()
94
+ // rebuilt alongside the index rather than pruned, so a deleted file's
95
+ // record cannot outlive it in a box that stays open all day
96
+ const nextSniffed = new Map<string, SniffedFile>()
80
97
  for (const relative of stdout.split('\0')) {
81
98
  if (!relative) continue
82
99
  let stat: fs.Stats
@@ -87,12 +104,22 @@ export class CheckoutFilePlane implements ProjectFilePlane {
87
104
  continue
88
105
  }
89
106
  if (!stat.isFile()) continue
107
+ const seen = this.sniffed.get(relative)
108
+ const binary =
109
+ seen && seen.size === stat.size && seen.mtimeMs === stat.mtimeMs
110
+ ? seen.binary
111
+ : looksBinary(path.join(this.root, relative), stat.size)
112
+ nextSniffed.set(relative, { size: stat.size, mtimeMs: stat.mtimeMs, binary })
90
113
  next.set(relative, {
91
114
  size: stat.size,
92
- readOnly: looksBinary(path.join(this.root, relative), stat.size),
115
+ // a checkout carries no write policy of its own; if the user can write
116
+ // the file, so can the shell
117
+ readOnly: false,
118
+ binary,
93
119
  })
94
120
  }
95
121
  this.entries = next
122
+ this.sniffed = nextSniffed
96
123
  }
97
124
 
98
125
  private absolute(planePath: string): string {
@@ -113,13 +140,15 @@ export class CheckoutFilePlane implements ProjectFilePlane {
113
140
  return fs.promises.readFile(this.absolute(planePath))
114
141
  }
115
142
 
116
- async write(planePath: string, content: string): Promise<void> {
143
+ async write(planePath: string, content: Uint8Array): Promise<void> {
117
144
  const absolute = this.absolute(planePath)
118
145
  await fs.promises.mkdir(path.dirname(absolute), { recursive: true })
119
- await fs.promises.writeFile(absolute, content, 'utf8')
146
+ await fs.promises.writeFile(absolute, content)
120
147
  this.entries.set(planePath, {
121
- size: Buffer.byteLength(content, 'utf8'),
148
+ size: content.byteLength,
122
149
  readOnly: false,
150
+ // the bytes are in hand, so the same NUL test costs nothing here
151
+ binary: content.subarray(0, BINARY_SNIFF_BYTES).includes(0),
123
152
  })
124
153
  }
125
154
 
@@ -1,3 +1,4 @@
1
+ import { rnxExit } from '../run-rnx'
1
2
  import { runTelemetry } from './telemetry'
2
3
 
3
4
  function printHelp(): void {
@@ -28,5 +29,5 @@ export async function runConfig(args: string[]): Promise<void> {
28
29
 
29
30
  console.error(` unknown config command: ${sub ?? '(missing)'}`)
30
31
  printHelp()
31
- process.exit(1)
32
+ rnxExit(1)
32
33
  }
@@ -55,6 +55,7 @@ import {
55
55
  } from '../current-sim'
56
56
  import { getDriver } from '../drivers/registry'
57
57
  import { openUrl as openUrlInBrowser } from '../open-url'
58
+ import { rethrowIfExit, rnxExit } from '../run-rnx'
58
59
  import {
59
60
  BridgeSimLockedError,
60
61
  bridgePortForShellBaseUrl,
@@ -477,18 +478,18 @@ function parseOpenRuntimeConfig(args: string[]): SootSimConfig | undefined {
477
478
  const raw = args[i + 1]
478
479
  if (!raw) {
479
480
  console.error(' rnx open: --replace expects <module>=<file>')
480
- process.exit(1)
481
+ rnxExit(1)
481
482
  }
482
483
  const eqIndex = raw.indexOf('=')
483
484
  if (eqIndex <= 0 || eqIndex === raw.length - 1) {
484
485
  console.error(' rnx open: --replace expects <module>=<file>')
485
- process.exit(1)
486
+ rnxExit(1)
486
487
  }
487
488
  const moduleName = raw.slice(0, eqIndex).trim()
488
489
  const filePath = normalizeReplacementPath(raw.slice(eqIndex + 1).trim())
489
490
  if (!fs.existsSync(filePath)) {
490
491
  console.error(` rnx open: replacement file not found: ${filePath}`)
491
- process.exit(1)
492
+ rnxExit(1)
492
493
  }
493
494
  modules[moduleName] = { file: filePath }
494
495
  i++
@@ -502,7 +503,7 @@ function parseOpenRuntimeConfig(args: string[]): SootSimConfig | undefined {
502
503
  const eqIndex = raw ? raw.indexOf('=') : -1
503
504
  if (!raw || eqIndex <= 0 || eqIndex === raw.length - 1) {
504
505
  console.error(' rnx open: --remap expects <host:port>=<host:port>')
505
- process.exit(1)
506
+ rnxExit(1)
506
507
  }
507
508
  remap[raw.slice(0, eqIndex).trim()] = raw.slice(eqIndex + 1).trim()
508
509
  i++
@@ -1338,7 +1339,7 @@ export async function runLinkCommand(args: string[], opts: ControlCommandOptions
1338
1339
  if (parsed.positional.length !== 1) {
1339
1340
  console.error(' rnx open: expected exactly one <url-or-route>')
1340
1341
  console.error(' examples: rnx open /settings | rnx open myapp://settings')
1341
- process.exit(1)
1342
+ rnxExit(1)
1342
1343
  }
1343
1344
 
1344
1345
  const bridge = createBridgeFromParsed(parsed)
@@ -1353,7 +1354,7 @@ export async function runLinkCommand(args: string[], opts: ControlCommandOptions
1353
1354
  warningsCommand: `rnx get warnings 5${simHint}`,
1354
1355
  requestsCommand: `rnx get requests 5${simHint}`,
1355
1356
  })
1356
- process.exit(1)
1357
+ rnxExit(1)
1357
1358
  } finally {
1358
1359
  bridge.close()
1359
1360
  }
@@ -1397,11 +1398,11 @@ export async function runOpenCommand(
1397
1398
  console.error(
1398
1399
  ` rnx open: --cdp-port must be a positive port number, got "${cdpPortArg}"`,
1399
1400
  )
1400
- process.exit(1)
1401
+ rnxExit(1)
1401
1402
  }
1402
1403
  if (requestedProfileId && ephemeralProfile) {
1403
1404
  console.error(' rnx open: --profile cannot be combined with --ephemeral')
1404
- process.exit(1)
1405
+ rnxExit(1)
1405
1406
  }
1406
1407
  const profileId = requestedProfileId ? ensureProfile(requestedProfileId).id : undefined
1407
1408
  const openInNewSim = args.includes('--new') || !!profileId || ephemeralProfile
@@ -1410,7 +1411,7 @@ export async function runOpenCommand(
1410
1411
  console.error(
1411
1412
  ' rnx open: --new, --profile, and --ephemeral cannot be combined with --sim',
1412
1413
  )
1413
- process.exit(1)
1414
+ rnxExit(1)
1414
1415
  }
1415
1416
  const target = parsed.positional[0] || ''
1416
1417
 
@@ -1419,7 +1420,7 @@ export async function runOpenCommand(
1419
1420
  console.error(
1420
1421
  ' rnx open: deep links target the current app; remove --new, --profile, or --ephemeral',
1421
1422
  )
1422
- process.exit(1)
1423
+ rnxExit(1)
1423
1424
  }
1424
1425
  await runLinkCommand(args, opts)
1425
1426
  return
@@ -1447,7 +1448,7 @@ export async function runOpenCommand(
1447
1448
  ` could not launch a browser: ${err instanceof Error ? err.message : String(err)}`,
1448
1449
  )
1449
1450
  console.error(` open it yourself: ${target}`)
1450
- process.exit(1)
1451
+ rnxExit(1)
1451
1452
  }
1452
1453
  console.log(
1453
1454
  isLocal
@@ -1492,7 +1493,7 @@ export async function runOpenCommand(
1492
1493
  const requestedDriverId = args.find((_, i) => args[i - 1] === '--driver') || ''
1493
1494
  if (requestedDriverId && !isDriverId(requestedDriverId)) {
1494
1495
  console.error(` unknown driver "${requestedDriverId}" — run \`rnx list --drivers\``)
1495
- process.exit(1)
1496
+ rnxExit(1)
1496
1497
  }
1497
1498
  const driverId: DriverId = isDriverId(requestedDriverId)
1498
1499
  ? requestedDriverId
@@ -1506,7 +1507,7 @@ export async function runOpenCommand(
1506
1507
  const driverHeadless = resolveDriverHeadless(args, driverId)
1507
1508
  if (cdpPort && driverId !== 'playwright') {
1508
1509
  console.error(' rnx open: --cdp-port is only honored by the playwright driver')
1509
- process.exit(1)
1510
+ rnxExit(1)
1510
1511
  }
1511
1512
 
1512
1513
  // metro HMR mode. interactive opens default to hot=true so edits hot-apply
@@ -1520,7 +1521,7 @@ export async function runOpenCommand(
1520
1521
  const noHmrFlag = args.includes('--no-hmr')
1521
1522
  if (hotFlag && noHmrFlag) {
1522
1523
  console.error(' rnx open: --hot and --no-hmr cannot be combined')
1523
- process.exit(1)
1524
+ rnxExit(1)
1524
1525
  }
1525
1526
  const hotMode = hotFlag ? true : noHmrFlag ? false : driverId !== 'playwright'
1526
1527
 
@@ -1532,7 +1533,7 @@ export async function runOpenCommand(
1532
1533
  if (deviceArg && !(deviceArg in devices)) {
1533
1534
  console.error(` rnx open: unknown device "${deviceArg}"`)
1534
1535
  console.error(` known devices: ${listSelectableDeviceModels().join(', ')}`)
1535
- process.exit(1)
1536
+ rnxExit(1)
1536
1537
  }
1537
1538
  const deviceSpec = deviceArg ? devices[deviceArg as DeviceModel] : undefined
1538
1539
 
@@ -1544,7 +1545,7 @@ export async function runOpenCommand(
1544
1545
  const viewportMatch = viewportArg ? /^(\d+)x(\d+)$/.exec(viewportArg) : null
1545
1546
  if (viewportArg && !viewportMatch) {
1546
1547
  console.error(` rnx open: --viewport must be <width>x<height>, got "${viewportArg}"`)
1547
- process.exit(1)
1548
+ rnxExit(1)
1548
1549
  }
1549
1550
  const explicitViewport = viewportMatch
1550
1551
  ? { width: Number(viewportMatch[1]), height: Number(viewportMatch[2]) }
@@ -1662,7 +1663,7 @@ export async function runOpenCommand(
1662
1663
  warningsCommand: `rnx get warnings 5${simHint}`,
1663
1664
  requestsCommand: `rnx get requests 5${simHint}`,
1664
1665
  })
1665
- process.exit(1)
1666
+ rnxExit(1)
1666
1667
  }
1667
1668
 
1668
1669
  if (!fallBackToNewSim && targetSim) {
@@ -1674,7 +1675,7 @@ export async function runOpenCommand(
1674
1675
  )
1675
1676
  if (!ready) {
1676
1677
  console.error(' timed out waiting for current sim to load target')
1677
- process.exit(1)
1678
+ rnxExit(1)
1678
1679
  }
1679
1680
  ready.bridge.close()
1680
1681
  saveCurrentSimId(targetSim.id)
@@ -1706,7 +1707,7 @@ export async function runOpenCommand(
1706
1707
  console.error(
1707
1708
  ' could not resolve a stable owning process; refusing unsupervised sim host',
1708
1709
  )
1709
- process.exit(1)
1710
+ rnxExit(1)
1710
1711
  }
1711
1712
  const simMatchesToken = (sim: BridgeSimInfo) =>
1712
1713
  sim.url?.includes(`inspectOpen=${token}`) ?? false
@@ -1714,7 +1715,7 @@ export async function runOpenCommand(
1714
1715
  const driver = getDriver(driverId)
1715
1716
  if (!driver) {
1716
1717
  console.error(` unknown driver "${driverId}" — run \`rnx list --drivers\``)
1717
- process.exit(1)
1718
+ rnxExit(1)
1718
1719
  }
1719
1720
  const connectWait = resolveDriverConnectWaitOptions()
1720
1721
  const result = await driver.launch({
@@ -1738,7 +1739,7 @@ export async function runOpenCommand(
1738
1739
  })
1739
1740
  if (!result.launched) {
1740
1741
  console.error(` ${driver.name} driver: ${result.message}`)
1741
- process.exit(1)
1742
+ rnxExit(1)
1742
1743
  }
1743
1744
  const outcome = await waitForSimMatchOrHostExit(
1744
1745
  parsed.wsPort,
@@ -1750,7 +1751,7 @@ export async function runOpenCommand(
1750
1751
  if (outcome.kind === 'host-exit') {
1751
1752
  printDriverDiagnosticLogTail(result)
1752
1753
  console.error(` ${driver.name} host crashed — sim is no longer attached`)
1753
- process.exit(1)
1754
+ rnxExit(1)
1754
1755
  }
1755
1756
  if (outcome.kind === 'timeout') {
1756
1757
  if (result.pid) {
@@ -1763,7 +1764,7 @@ export async function runOpenCommand(
1763
1764
  console.error(
1764
1765
  ` timed out after ${connectWait.timeoutMs}ms waiting for opened sim to connect`,
1765
1766
  )
1766
- process.exit(1)
1767
+ rnxExit(1)
1767
1768
  }
1768
1769
  const match = outcome.sim
1769
1770
  saveCurrentSimId(match.id)
@@ -1801,7 +1802,7 @@ async function runUseLikeCommand(args: string[], opts: ControlCommandOptions = {
1801
1802
  ` refused: ${target.id} is locked by the active user (${secs}s) — that's a live human tab`,
1802
1803
  )
1803
1804
  console.error(` run \`rnx open --new\` for a fresh investigation sim`)
1804
- process.exit(1)
1805
+ rnxExit(1)
1805
1806
  }
1806
1807
  if (!force) {
1807
1808
  // another CLI agent leases it. don't hijack its target silently;
@@ -1813,20 +1814,21 @@ async function runUseLikeCommand(args: string[], opts: ControlCommandOptions = {
1813
1814
  console.error(
1814
1815
  ` \`sootsim use ${target.id} --force\` to take it, or \`rnx open --new\` for a fresh sim`,
1815
1816
  )
1816
- process.exit(1)
1817
+ rnxExit(1)
1817
1818
  }
1818
1819
  }
1819
1820
  await bridge.focusSim(target.id)
1820
1821
  saveCurrentSimId(target.id)
1821
1822
  console.log(` using: ${target.id}`)
1822
1823
  } catch (err) {
1824
+ rethrowIfExit(err)
1823
1825
  console.error(` use failed: ${err instanceof Error ? err.message : String(err)}`)
1824
1826
  await printBridgeFailureDiagnostics(bridge, {
1825
1827
  errorsCommand: `rnx get errors 5${simHint}`,
1826
1828
  warningsCommand: `rnx get warnings 5${simHint}`,
1827
1829
  requestsCommand: `rnx get requests 5${simHint}`,
1828
1830
  })
1829
- process.exit(1)
1831
+ rnxExit(1)
1830
1832
  }
1831
1833
  } finally {
1832
1834
  bridge.close()
@@ -1871,10 +1873,10 @@ export async function runClaimCommand(args: string[], opts: ControlCommandOption
1871
1873
  const secs = Math.max(0, Math.round(err.lock.expiresInMs / 1000))
1872
1874
  console.error(` claim failed: locked by ${err.lock.by} for ${secs}s more`)
1873
1875
  console.error(` use --force to take it, or \`rnx open --new\` for a fresh sim`)
1874
- process.exit(1)
1876
+ rnxExit(1)
1875
1877
  }
1876
1878
  console.error(` claim failed: ${err instanceof Error ? err.message : String(err)}`)
1877
- process.exit(1)
1879
+ rnxExit(1)
1878
1880
  }
1879
1881
  } finally {
1880
1882
  bridge.close()
@@ -2016,7 +2018,7 @@ export async function runCloseCommand(args: string[], opts: ControlCommandOption
2016
2018
  console.error(
2017
2019
  ` close failed: keep sim ${plan.missingExplicitKeepId} is not connected; not closing other sims`,
2018
2020
  )
2019
- process.exit(1)
2021
+ rnxExit(1)
2020
2022
  }
2021
2023
  if (plan.targets.length === 0) {
2022
2024
  console.log(
@@ -2047,11 +2049,12 @@ export async function runCloseCommand(args: string[], opts: ControlCommandOption
2047
2049
  console.log(summary)
2048
2050
  if (result.remaining.length > 0) {
2049
2051
  console.error(` close failed: still connected: ${result.remaining.join(', ')}`)
2050
- process.exit(1)
2052
+ rnxExit(1)
2051
2053
  }
2052
2054
  } catch (err) {
2055
+ rethrowIfExit(err)
2053
2056
  console.error(` close failed: ${err instanceof Error ? err.message : String(err)}`)
2054
- process.exit(1)
2057
+ rnxExit(1)
2055
2058
  } finally {
2056
2059
  bridge.close()
2057
2060
  }
@@ -2074,7 +2077,7 @@ export async function runCloseCommand(args: string[], opts: ControlCommandOption
2074
2077
  if (!closed) {
2075
2078
  await terminatePlaywrightHostsForSims(sims, [target.id])
2076
2079
  console.error(` close failed: ${target.id} is still connected`)
2077
- process.exit(1)
2080
+ rnxExit(1)
2078
2081
  }
2079
2082
  await terminatePlaywrightHostsForSims(sims, [target.id])
2080
2083
  if (readCurrentSimId() === target.id) {
@@ -2083,13 +2086,14 @@ export async function runCloseCommand(args: string[], opts: ControlCommandOption
2083
2086
  }
2084
2087
  console.log(` closed: ${target.id}`)
2085
2088
  } catch (err) {
2089
+ rethrowIfExit(err)
2086
2090
  console.error(` close failed: ${err instanceof Error ? err.message : String(err)}`)
2087
2091
  await printBridgeFailureDiagnostics(bridge, {
2088
2092
  errorsCommand: `rnx get errors 5${simHint}`,
2089
2093
  warningsCommand: `rnx get warnings 5${simHint}`,
2090
2094
  requestsCommand: `rnx get requests 5${simHint}`,
2091
2095
  })
2092
- process.exit(1)
2096
+ rnxExit(1)
2093
2097
  }
2094
2098
  } finally {
2095
2099
  bridge.close()
@@ -37,6 +37,7 @@ import {
37
37
  } from '../../src/home-paths'
38
38
  import { rnxPublicBrand } from '../../src/public-brand'
39
39
  import { printCommandHelp } from '../help'
40
+ import { rnxExit } from '../run-rnx'
40
41
  import { ensureDaemonApp } from './daemon-mac-app'
41
42
 
42
43
  // minimum seconds between launchd / systemd respawns. without this, a daemon
@@ -82,7 +83,7 @@ export async function runDaemon(args: string[], opts: DaemonOptions = {}) {
82
83
  default:
83
84
  console.error(` unknown daemon subcommand: ${sub}`)
84
85
  printHelp()
85
- process.exit(1)
86
+ rnxExit(1)
86
87
  }
87
88
  }
88
89
 
@@ -468,7 +469,7 @@ export async function daemonInstall({ port, force }: { port: number; force: bool
468
469
 
469
470
  console.error(` background daemon is not supported on ${process.platform}`)
470
471
  console.error(` run 'rnx serve' in a persistent shell instead`)
471
- process.exit(1)
472
+ rnxExit(1)
472
473
  }
473
474
 
474
475
  // removed-path log (idempotent helpers track what they actually touched so the
@@ -649,7 +650,7 @@ async function daemonStatus() {
649
650
  return
650
651
  }
651
652
  console.error(` unsupported platform: ${process.platform}`)
652
- process.exit(1)
653
+ rnxExit(1)
653
654
  }
654
655
 
655
656
  async function daemonRestart() {
@@ -657,7 +658,7 @@ async function daemonRestart() {
657
658
  const path = macPlistPath()
658
659
  if (!existsSync(path)) {
659
660
  console.error(' daemon not registered. run `rnx daemon install`')
660
- process.exit(1)
661
+ rnxExit(1)
661
662
  }
662
663
  const res = spawnSync(
663
664
  'launchctl',
@@ -666,7 +667,7 @@ async function daemonRestart() {
666
667
  )
667
668
  if (res.status !== 0) {
668
669
  console.error(` kickstart failed: ${res.stderr?.trim()}`)
669
- process.exit(1)
670
+ rnxExit(1)
670
671
  }
671
672
  console.log(' restarted')
672
673
  return
@@ -675,7 +676,7 @@ async function daemonRestart() {
675
676
  const path = linuxUnitPath()
676
677
  if (!existsSync(path)) {
677
678
  console.error(' daemon not registered. run `rnx daemon install`')
678
- process.exit(1)
679
+ rnxExit(1)
679
680
  }
680
681
  const res = spawnSync('systemctl', ['--user', 'restart', SERVICE_NAME_LINUX], {
681
682
  stdio: 'pipe',
@@ -683,13 +684,13 @@ async function daemonRestart() {
683
684
  })
684
685
  if (res.status !== 0) {
685
686
  console.error(` restart failed: ${res.stderr?.trim()}`)
686
- process.exit(1)
687
+ rnxExit(1)
687
688
  }
688
689
  console.log(' restarted')
689
690
  return
690
691
  }
691
692
  console.error(` unsupported platform: ${process.platform}`)
692
- process.exit(1)
693
+ rnxExit(1)
693
694
  }
694
695
 
695
696
  async function daemonStart() {
@@ -697,7 +698,7 @@ async function daemonStart() {
697
698
  const path = macPlistPath()
698
699
  if (!existsSync(path)) {
699
700
  console.error(' daemon not registered. run `rnx daemon install`')
700
- process.exit(1)
701
+ rnxExit(1)
701
702
  }
702
703
  const res = spawnSync(
703
704
  'launchctl',
@@ -706,7 +707,7 @@ async function daemonStart() {
706
707
  )
707
708
  if (res.status !== 0) {
708
709
  console.error(` start failed: ${res.stderr?.trim()}`)
709
- process.exit(1)
710
+ rnxExit(1)
710
711
  }
711
712
  console.log(' started')
712
713
  return
@@ -715,7 +716,7 @@ async function daemonStart() {
715
716
  const path = linuxUnitPath()
716
717
  if (!existsSync(path)) {
717
718
  console.error(' daemon not registered. run `rnx daemon install`')
718
- process.exit(1)
719
+ rnxExit(1)
719
720
  }
720
721
  const res = spawnSync('systemctl', ['--user', 'start', SERVICE_NAME_LINUX], {
721
722
  stdio: 'pipe',
@@ -723,13 +724,13 @@ async function daemonStart() {
723
724
  })
724
725
  if (res.status !== 0) {
725
726
  console.error(` start failed: ${res.stderr?.trim()}`)
726
- process.exit(1)
727
+ rnxExit(1)
727
728
  }
728
729
  console.log(' started')
729
730
  return
730
731
  }
731
732
  console.error(` unsupported platform: ${process.platform}`)
732
- process.exit(1)
733
+ rnxExit(1)
733
734
  }
734
735
 
735
736
  async function daemonStop() {
@@ -737,7 +738,7 @@ async function daemonStop() {
737
738
  const path = macPlistPath()
738
739
  if (!existsSync(path)) {
739
740
  console.error(' daemon not registered — nothing to stop')
740
- process.exit(1)
741
+ rnxExit(1)
741
742
  }
742
743
  const res = spawnSync(
743
744
  'launchctl',
@@ -746,7 +747,7 @@ async function daemonStop() {
746
747
  )
747
748
  if (res.status !== 0) {
748
749
  console.error(` stop failed: ${res.stderr?.trim()}`)
749
- process.exit(1)
750
+ rnxExit(1)
750
751
  }
751
752
  console.log(' stopped')
752
753
  return
@@ -755,7 +756,7 @@ async function daemonStop() {
755
756
  const path = linuxUnitPath()
756
757
  if (!existsSync(path)) {
757
758
  console.error(' daemon not registered — nothing to stop')
758
- process.exit(1)
759
+ rnxExit(1)
759
760
  }
760
761
  const res = spawnSync('systemctl', ['--user', 'stop', SERVICE_NAME_LINUX], {
761
762
  stdio: 'pipe',
@@ -763,13 +764,13 @@ async function daemonStop() {
763
764
  })
764
765
  if (res.status !== 0) {
765
766
  console.error(` stop failed: ${res.stderr?.trim()}`)
766
- process.exit(1)
767
+ rnxExit(1)
767
768
  }
768
769
  console.log(' stopped')
769
770
  return
770
771
  }
771
772
  console.error(` unsupported platform: ${process.platform}`)
772
- process.exit(1)
773
+ rnxExit(1)
773
774
  }
774
775
 
775
776
  // tail the daemon's logs. macOS reads the launchd-redirected files directly
@@ -789,7 +790,7 @@ async function daemonLogs(rest: string[]) {
789
790
  const present = [out, err].filter((p) => existsSync(p))
790
791
  if (present.length === 0) {
791
792
  console.error(` no daemon logs yet at ${LOG_DIR}. run \`rnx daemon install\``)
792
- process.exit(1)
793
+ rnxExit(1)
793
794
  }
794
795
  const args = follow
795
796
  ? ['-n', String(lines), '-F', ...present]
@@ -829,11 +830,11 @@ async function daemonLogs(rest: string[]) {
829
830
  ` could not read --user journal for ${unit} (this user isn't in the systemd-journal group yet).\n` +
830
831
  ` run: sudo journalctl _SYSTEMD_USER_UNIT=${unit} -n ${lines} --no-pager`,
831
832
  )
832
- process.exit(1)
833
+ rnxExit(1)
833
834
  }
834
835
 
835
836
  console.error(` unsupported platform: ${process.platform}`)
836
- process.exit(1)
837
+ rnxExit(1)
837
838
  }
838
839
 
839
840
  function readPlistBinary(path: string): string | null {