rnxsim 0.1.351 → 0.1.353

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 (198) hide show
  1. package/README.md +17 -16
  2. package/cli/bin.ts +5 -4
  3. package/cli/commands/control.ts +20 -5
  4. package/cli/commands/daemon-mac-app.ts +1 -1
  5. package/cli/commands/daemon.ts +6 -8
  6. package/cli/commands/install-desktop.ts +42 -32
  7. package/cli/commands/no-bridge-hint.ts +3 -3
  8. package/cli/commands/serve.ts +1 -1
  9. package/cli/commands/setup.ts +215 -176
  10. package/cli/commands/welcome.ts +9 -58
  11. package/cli/desktop-companion.ts +31 -16
  12. package/cli/prompt.ts +12 -7
  13. package/cli/setup-repository.ts +6 -2
  14. package/cli/ui.ts +68 -0
  15. package/dist-cli/bin.js +5 -5
  16. package/dist-cli/chunks/{agent-6RLIEC2X.js → agent-YTAX6OQQ.js} +2 -2
  17. package/dist-cli/chunks/{agent-wrapper-CVNCDW2X.js → agent-wrapper-2QSUXMNK.js} +2 -2
  18. package/dist-cli/chunks/{app-fonts-6I2E2HJS.js → app-fonts-4EVHPXVG.js} +2 -2
  19. package/dist-cli/chunks/{assert-MNEHW5CU.js → assert-IXMSQLVY.js} +2 -2
  20. package/dist-cli/chunks/{auth-EMBA257X.js → auth-6QFWU2T5.js} +2 -2
  21. package/dist-cli/chunks/{beta-N5UNBGOB.js → beta-27I5WWRE.js} +2 -2
  22. package/dist-cli/chunks/{camera-7BC6VI22.js → camera-4HFUBXPM.js} +2 -2
  23. package/dist-cli/chunks/{chunk-WT3LTPC3.js → chunk-37J74ASV.js} +1 -1
  24. package/dist-cli/chunks/{chunk-JNFFNKYD.js → chunk-3IWR7KHR.js} +2 -2
  25. package/dist-cli/chunks/{chunk-QBTL2KKC.js → chunk-3UXMNDMH.js} +2 -2
  26. package/dist-cli/chunks/{chunk-FD5I26XT.js → chunk-3XDGLCNP.js} +2 -2
  27. package/dist-cli/chunks/{chunk-JYZNBS2I.js → chunk-4IXIONDR.js} +2 -2
  28. package/dist-cli/chunks/{chunk-UCXJVNXO.js → chunk-4NPZ5FO2.js} +2 -2
  29. package/dist-cli/chunks/{chunk-74SJBJWS.js → chunk-4PV6QASJ.js} +1 -1
  30. package/dist-cli/chunks/{chunk-6DKCZQWZ.js → chunk-57YZEJ6J.js} +1 -1
  31. package/dist-cli/chunks/{chunk-DW5FZOZZ.js → chunk-5DLBRZAG.js} +2 -2
  32. package/dist-cli/chunks/{chunk-JIAE2FGB.js → chunk-6JBOKETZ.js} +2 -2
  33. package/dist-cli/chunks/{chunk-ZE4AZMHA.js → chunk-6NE5Z6KG.js} +1 -1
  34. package/dist-cli/chunks/{chunk-X4TOCIFN.js → chunk-7FUSVOMH.js} +2 -2
  35. package/dist-cli/chunks/{chunk-XWPVWZKG.js → chunk-7SKQRS74.js} +1 -1
  36. package/dist-cli/chunks/chunk-7V5O6I4Q.js +9 -0
  37. package/dist-cli/chunks/{chunk-WEWSQLTT.js → chunk-BK5TJXUU.js} +2 -2
  38. package/dist-cli/chunks/{chunk-GZQ3SQ4N.js → chunk-CMQIFA4L.js} +1 -1
  39. package/dist-cli/chunks/{chunk-ITCT4D3M.js → chunk-CYS3D7RG.js} +2 -2
  40. package/dist-cli/chunks/{chunk-O72VINK4.js → chunk-DSBUGNC5.js} +2 -2
  41. package/dist-cli/chunks/{chunk-PJX2ZXWC.js → chunk-ECIDT6F5.js} +1 -1
  42. package/dist-cli/chunks/{chunk-TE3WVO5B.js → chunk-ELBDKQK2.js} +2 -2
  43. package/dist-cli/chunks/{chunk-LV2N6CXX.js → chunk-GAYCF2O5.js} +1 -1
  44. package/dist-cli/chunks/{chunk-MIZWPUZE.js → chunk-HVLZYUGF.js} +2 -2
  45. package/dist-cli/chunks/{chunk-UZWKD5BM.js → chunk-HWMBTYJX.js} +2 -2
  46. package/dist-cli/chunks/{chunk-5C7EQOPN.js → chunk-HZTWQZUD.js} +2 -2
  47. package/dist-cli/chunks/{chunk-PBKO6VQY.js → chunk-IX7W5BXL.js} +2 -2
  48. package/dist-cli/chunks/{chunk-CJC2FL5A.js → chunk-JVVO63QH.js} +1 -1
  49. package/dist-cli/chunks/{chunk-VOX5M4H2.js → chunk-KIQVNLVD.js} +2 -2
  50. package/dist-cli/chunks/chunk-KZLGVYCX.js +7 -0
  51. package/dist-cli/chunks/{chunk-GBWVWPZP.js → chunk-LAFKQUT7.js} +3 -3
  52. package/dist-cli/chunks/{chunk-YMFI4QND.js → chunk-LMFAIAA5.js} +1 -1
  53. package/dist-cli/chunks/chunk-MA5E6HJR.js +15 -0
  54. package/dist-cli/chunks/chunk-OEU2MEZQ.js +124 -0
  55. package/dist-cli/chunks/{chunk-VABPQGH2.js → chunk-PBTG4UCS.js} +1 -1
  56. package/dist-cli/chunks/{chunk-BHPAPSQ5.js → chunk-PNQ33FJB.js} +6 -6
  57. package/dist-cli/chunks/{chunk-LC2JTBH7.js → chunk-R4KBCGXX.js} +3 -3
  58. package/dist-cli/chunks/chunk-RMOSWLWG.js +32 -0
  59. package/dist-cli/chunks/{chunk-WR5WQC5K.js → chunk-RRX5RZI2.js} +1 -1
  60. package/dist-cli/chunks/{chunk-UBVT72BP.js → chunk-T2ZZSSCP.js} +2 -2
  61. package/dist-cli/chunks/{chunk-QAAWQVE6.js → chunk-TX2T5KPQ.js} +2 -2
  62. package/dist-cli/chunks/{chunk-VXTDGO3L.js → chunk-UEILOAPW.js} +1 -1
  63. package/dist-cli/chunks/{chunk-DVDSTTMN.js → chunk-UNBVUOIC.js} +2 -2
  64. package/dist-cli/chunks/chunk-UQBXA3AK.js +24 -0
  65. package/dist-cli/chunks/{chunk-I2R35E6G.js → chunk-V7X543SB.js} +1 -1
  66. package/dist-cli/chunks/{chunk-AY3OOK3P.js → chunk-VLI54BKH.js} +2 -2
  67. package/dist-cli/chunks/{chunk-RTZGWDWV.js → chunk-VZDW2U64.js} +2 -2
  68. package/dist-cli/chunks/chunk-WKQ6W74M.js +11 -0
  69. package/dist-cli/chunks/{chunk-45WYB6FX.js → chunk-WTGQUQ32.js} +2 -2
  70. package/dist-cli/chunks/{chunk-NNSEC5MQ.js → chunk-WU25RTIX.js} +2 -2
  71. package/dist-cli/chunks/chunk-WV3TKN7D.js +4 -0
  72. package/dist-cli/chunks/{chunk-SLD7GMVV.js → chunk-XVLYLZOM.js} +2 -2
  73. package/dist-cli/chunks/{chunk-G5ZQWJDD.js → chunk-YQJOM55S.js} +1 -1
  74. package/dist-cli/chunks/{chunk-KLHHV2TO.js → chunk-YYYWU6LL.js} +3 -3
  75. package/dist-cli/chunks/{chunk-TBBPDV7M.js → chunk-ZF53H457.js} +2 -2
  76. package/dist-cli/chunks/{chunk-QAWBYXGD.js → chunk-ZJ4O636U.js} +1 -1
  77. package/dist-cli/chunks/{chunk-4DDUJL5W.js → chunk-ZXHAAVTH.js} +2 -2
  78. package/dist-cli/chunks/{chunk-HDSRLL3R.js → chunk-ZYGWIVW4.js} +2 -2
  79. package/dist-cli/chunks/{cleanup-CWI5IGHC.js → cleanup-ULH3BHAE.js} +2 -2
  80. package/dist-cli/chunks/cli-version-I2E56I4W.js +4 -0
  81. package/dist-cli/chunks/{compat-KV4HO7EC.js → compat-ONN5DOHB.js} +2 -2
  82. package/dist-cli/chunks/{config-YZZGDTOS.js → config-OPZU73MX.js} +2 -2
  83. package/dist-cli/chunks/control-WNYEAI4K.js +4 -0
  84. package/dist-cli/chunks/daemon-5J6V2GLG.js +4 -0
  85. package/dist-cli/chunks/{debug-DBEYUDT2.js → debug-GNI3HDAK.js} +3 -3
  86. package/dist-cli/chunks/{desktop-GUEM7QU6.js → desktop-DF65PDJG.js} +4 -4
  87. package/dist-cli/chunks/{detox-5X7ITQH3.js → detox-N267KW2Y.js} +2 -2
  88. package/dist-cli/chunks/{device-BMFMUVBB.js → device-RFU44PE3.js} +2 -2
  89. package/dist-cli/chunks/{diagnose-BUDXONPK.js → diagnose-XR2JIGDE.js} +2 -2
  90. package/dist-cli/chunks/{disk-cleanup-NXEYXGDW.js → disk-cleanup-DPY6TYXA.js} +2 -2
  91. package/dist-cli/chunks/drivers-OIZC544W.js +4 -0
  92. package/dist-cli/chunks/{film-V5NXEAOE.js → film-NQQZPZ2W.js} +3 -3
  93. package/dist-cli/chunks/flow-3NHEHRF4.js +4 -0
  94. package/dist-cli/chunks/help-VACWNWEH.js +4 -0
  95. package/dist-cli/chunks/{hidden-runtime-alias-AMORND7B.js → hidden-runtime-alias-APCBIXJ6.js} +2 -2
  96. package/dist-cli/chunks/home-paths-27GY5EPD.js +4 -0
  97. package/dist-cli/chunks/inspect-2JELKZ5A.js +4 -0
  98. package/dist-cli/chunks/install-desktop-4R7UTGQU.js +4 -0
  99. package/dist-cli/chunks/{login-HSSJFLIZ.js → login-6KIO5FR4.js} +2 -2
  100. package/dist-cli/chunks/{logout-NHTOFPX7.js → logout-WMCYGTYA.js} +2 -2
  101. package/dist-cli/chunks/{maestro-NBP7MXVI.js → maestro-7QLOUZIE.js} +3 -3
  102. package/dist-cli/chunks/{maestro-generate-YLDGD3LW.js → maestro-generate-QIGCQANR.js} +3 -3
  103. package/dist-cli/chunks/{mode-VJ4SI22C.js → mode-3H662MUJ.js} +2 -2
  104. package/dist-cli/chunks/{optional-demo-registry-BLL3OLIY.js → optional-demo-registry-FQV54ASP.js} +2 -2
  105. package/dist-cli/chunks/{perf-WGXXCWH3.js → perf-KC6LOGFD.js} +2 -2
  106. package/dist-cli/chunks/{permissions-YBBBMZKX.js → permissions-UGVHA3L6.js} +2 -2
  107. package/dist-cli/chunks/privacy-E6KH2DRJ.js +4 -0
  108. package/dist-cli/chunks/{record-IJ7CXLBJ.js → record-V72Q363I.js} +3 -3
  109. package/dist-cli/chunks/{report-issue-VKEGH7UZ.js → report-issue-MILOOCMG.js} +2 -2
  110. package/dist-cli/chunks/reset-LEL5ZGFN.js +4 -0
  111. package/dist-cli/chunks/runtime-RT2OTIQC.js +4 -0
  112. package/dist-cli/chunks/{screenshot-command-OFGD7KW4.js → screenshot-command-FYMZF5AF.js} +3 -3
  113. package/dist-cli/chunks/{screenshot-layers-KTZE2NKL.js → screenshot-layers-OXLW6GEJ.js} +2 -2
  114. package/dist-cli/chunks/{screenshots-capture-3V3N3B6Y.js → screenshots-capture-N3LKTUKB.js} +2 -2
  115. package/dist-cli/chunks/{serve-GXB23QJ5.js → serve-USDREK3N.js} +2 -2
  116. package/dist-cli/chunks/setup-QC7W7NCL.js +4 -0
  117. package/dist-cli/chunks/{skills-FULZE256.js → skills-3MIZF3DS.js} +2 -2
  118. package/dist-cli/chunks/{state-ELU6WDTK.js → state-MCFLWQHV.js} +2 -2
  119. package/dist-cli/chunks/{storage-YSRCHZLY.js → storage-KXNS5WEC.js} +2 -2
  120. package/dist-cli/chunks/store-GL6DFPJL.js +4 -0
  121. package/dist-cli/chunks/telemetry-OOW5FMZQ.js +4 -0
  122. package/dist-cli/chunks/{timeline-HORKYNWZ.js → timeline-QJ3USMSX.js} +2 -2
  123. package/dist-cli/chunks/{upgrade-UD2KNVMF.js → upgrade-LXFD4DFX.js} +2 -2
  124. package/dist-cli/chunks/upload-EUEO4D5N.js +4 -0
  125. package/dist-cli/chunks/{version-VCSVJBED.js → version-D5DLNPHP.js} +2 -2
  126. package/dist-cli/chunks/{web-JCIW3DXD.js → web-FRFNTTZ4.js} +2 -2
  127. package/dist-cli/chunks/welcome-YYYGS5WD.js +5 -0
  128. package/dist-cli/chunks/{what-happened-SMNGAT2G.js → what-happened-FJ6GRQAS.js} +2 -2
  129. package/dist-lib/agent-daemon-client.cjs +2 -2
  130. package/dist-lib/agent-events.cjs +1 -1
  131. package/dist-lib/agent-identity.cjs +1 -1
  132. package/dist-lib/agent-sessions.cjs +1 -1
  133. package/dist-lib/attached-projects.cjs +1 -1
  134. package/dist-lib/auth/shared-session.cjs +1 -1
  135. package/dist-lib/backend-origin.cjs +1 -1
  136. package/dist-lib/beta.cjs +1 -1
  137. package/dist-lib/beta.mjs +1 -1
  138. package/dist-lib/bridge-constants.cjs +1 -1
  139. package/dist-lib/bridge-contract.cjs +1 -1
  140. package/dist-lib/cli-constants.cjs +1 -1
  141. package/dist-lib/config.cjs +1 -1
  142. package/dist-lib/detox/index.cjs +1 -1
  143. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  144. package/dist-lib/home-paths.cjs +21 -1
  145. package/dist-lib/host/bridge-host.cjs +1 -1
  146. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  147. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  148. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  149. package/dist-lib/host/replacement-module-handler.cjs +1 -1
  150. package/dist-lib/host/websocket-proxy.cjs +1 -1
  151. package/dist-lib/index.cjs +2 -2
  152. package/dist-lib/jump-to-source-babel.cjs +1 -1
  153. package/dist-lib/menu.cjs +1 -1
  154. package/dist-lib/menu.mjs +1 -1
  155. package/dist-lib/metro-production-bundle.cjs +1 -1
  156. package/dist-lib/metro-production-bundle.mjs +1 -1
  157. package/dist-lib/metro.cjs +2 -2
  158. package/dist-lib/profiles.cjs +1 -1
  159. package/dist-lib/public-brand.cjs +1 -1
  160. package/dist-lib/react-native-host-modules.cjs +1 -1
  161. package/dist-lib/react-native-host-modules.mjs +1 -1
  162. package/dist-lib/render-mode.cjs +1 -1
  163. package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
  164. package/dist-lib/sdk.cjs +1 -1
  165. package/dist-lib/sdk.mjs +1 -1
  166. package/dist-lib/skills.cjs +87 -22
  167. package/dist-lib/vite.cjs +2 -2
  168. package/package.json +1 -1
  169. package/skills/contrast/SKILL.md +3 -3
  170. package/skills/rnx-setup/SKILL.md +17 -5
  171. package/src/agent-daemon-client.ts +1 -1
  172. package/src/home-paths.ts +22 -0
  173. package/src/metro-plugin.ts +1 -1
  174. package/src/runtime-assets.ts +1 -1
  175. package/dist-cli/chunks/chunk-ETM6XPHC.js +0 -124
  176. package/dist-cli/chunks/chunk-JWTYMJAQ.js +0 -11
  177. package/dist-cli/chunks/chunk-KEY4GEFZ.js +0 -15
  178. package/dist-cli/chunks/chunk-MMAIG4KO.js +0 -4
  179. package/dist-cli/chunks/chunk-O2UXRAUY.js +0 -7
  180. package/dist-cli/chunks/chunk-SBQZEOFA.js +0 -6
  181. package/dist-cli/chunks/chunk-SJLPG4M2.js +0 -25
  182. package/dist-cli/chunks/cli-version-MUSCJBJ6.js +0 -4
  183. package/dist-cli/chunks/control-ZBIYV5NJ.js +0 -4
  184. package/dist-cli/chunks/daemon-BZ522KVR.js +0 -4
  185. package/dist-cli/chunks/drivers-R6ZODPTZ.js +0 -4
  186. package/dist-cli/chunks/flow-VRIQ3QT3.js +0 -4
  187. package/dist-cli/chunks/help-ZDWHZHKJ.js +0 -4
  188. package/dist-cli/chunks/home-paths-MMW3RXAY.js +0 -4
  189. package/dist-cli/chunks/inspect-AD4DSQKO.js +0 -4
  190. package/dist-cli/chunks/install-desktop-7BNS5T3G.js +0 -4
  191. package/dist-cli/chunks/privacy-VDOOIAVC.js +0 -4
  192. package/dist-cli/chunks/reset-JHHQAVAU.js +0 -4
  193. package/dist-cli/chunks/runtime-SZDEN2UT.js +0 -4
  194. package/dist-cli/chunks/setup-JBAKC7CE.js +0 -30
  195. package/dist-cli/chunks/store-VZ65YN6Q.js +0 -4
  196. package/dist-cli/chunks/telemetry-I6B4AHRX.js +0 -4
  197. package/dist-cli/chunks/upload-5HKXUUEK.js +0 -4
  198. package/dist-cli/chunks/welcome-Y25RZNET.js +0 -5
@@ -1,36 +1,44 @@
1
- // sootsim setup the single guided machine and repository onboarding flow.
1
+ // rnx setup owns the optional machine and repository choices. rnx itself
2
+ // works without project setup; bare first-run onboarding launches ConnectRN
3
+ // during this flow and leaves the repository choice until the end.
2
4
 
3
- import fs from 'node:fs'
4
5
  import os from 'node:os'
5
6
  import path from 'node:path'
6
- import {
7
- COMPAT_RESULT_CAVEAT,
8
- formatDetailedScanLines,
9
- formatScanCounts,
10
- scanDeps,
11
- type ScanResult,
12
- } from '@sootsim/compat/web'
13
7
  import { DEFAULT_SOOTSIM_BRIDGE_PORT } from '../../src/bridge-constants'
14
8
  import { getCliVersion } from '../../src/cli-version'
15
9
  import {
16
10
  readActiveRuntime,
17
11
  readPrivacyPreferences,
12
+ readSimulatorDriverPreference,
18
13
  runtimeDir,
14
+ type SimulatorDriverPreference,
15
+ writeOnboardingPreferences,
19
16
  writePrivacyPreferences,
20
17
  } from '../../src/home-paths'
21
18
  import { sootsimRuntime } from '../../src/runtime-delivery'
22
- import { confirm } from '../prompt'
19
+ import { findDesktopCompanion } from '../desktop-companion'
20
+ import { confirm, select } from '../prompt'
23
21
  import {
24
22
  applyRepositorySetup,
25
- detectPreferredPackageManager,
26
- findPackageRoot,
23
+ findRepositoryPackageRoot,
27
24
  formatRepositoryOperations,
28
25
  planRepositorySetup,
29
26
  type RepositorySetupPlan,
30
27
  } from '../setup-repository'
31
28
  import { trackCliError } from '../telemetry'
29
+ import {
30
+ accent,
31
+ bold,
32
+ muted,
33
+ printBrand,
34
+ printPromptBlock,
35
+ success,
36
+ withSpinner,
37
+ } from '../ui'
38
+ import { runOpenCommand } from './control'
32
39
  import { daemonInstall, getDaemonServiceStatus } from './daemon'
33
40
  import { isAgentEnv } from './inspect/env'
41
+ import { runInstallDesktop } from './install-desktop'
34
42
 
35
43
  interface SetupOptions {
36
44
  appDir: string
@@ -41,107 +49,155 @@ interface SetupOptions {
41
49
  service: boolean | undefined
42
50
  repository: boolean | undefined
43
51
  productAnalytics: boolean | undefined
52
+ simulatorDriver: SimulatorDriverPreference | undefined
53
+ }
54
+
55
+ interface SetupRunOptions {
56
+ launchConnectRN?: boolean
44
57
  }
45
58
 
46
59
  export interface SetupResult {
47
60
  runtimeVersion: string
48
61
  serviceInstalled: boolean
49
62
  repositoryPrepared: boolean
63
+ simulatorDriver: SimulatorDriverPreference
50
64
  }
51
65
 
52
- export async function runSetup(args: string[]): Promise<SetupResult | undefined> {
66
+ export async function runSetup(
67
+ args: string[],
68
+ runOptions: SetupRunOptions = {},
69
+ ): Promise<SetupResult | undefined> {
53
70
  if (args.includes('--help') || args.includes('-h')) {
54
71
  printHelp()
55
72
  return
56
73
  }
57
74
 
58
75
  const options = parseSetupOptions(args)
76
+ const launchConnectRN =
77
+ runOptions.launchConnectRN ??
78
+ (!options.ci &&
79
+ !options.yes &&
80
+ !options.json &&
81
+ !options.dryRun &&
82
+ process.stdin.isTTY)
59
83
  try {
60
- const packageRoot = findPackageRoot(options.appDir)
84
+ printIntro(options)
85
+
86
+ const packageRoot = findRepositoryPackageRoot(options.appDir)
61
87
  const serviceStatus = getDaemonServiceStatus()
62
88
  const privacy = readPrivacyPreferences()
63
89
  const service = await decideService(options, serviceStatus)
64
- const repository = await decideRepository(options, packageRoot)
65
- const productAnalytics = await decidePrivacy(options, privacy)
66
- const crashReports = false
67
-
68
- const manifest = await sootsimRuntime.fetchManifest()
69
- const resolvedRuntime = sootsimRuntime.resolveVersion(manifest, { channel: 'stable' })
70
- const runtimeVersion = resolvedRuntime.version
90
+ if (!service && !serviceStatus.installed && !options.json) {
91
+ console.log(` ${muted('Enable it later with')} ${accent('rnx daemon install')}`)
92
+ }
93
+ const simulatorDriver = await decideSimulatorDriver(options)
94
+
95
+ const manifest = options.json
96
+ ? await sootsimRuntime.fetchManifest()
97
+ : await withSpinner('Checking the rnx runtime', () =>
98
+ sootsimRuntime.fetchManifest(),
99
+ )
100
+ const runtimeVersion = sootsimRuntime.resolveVersion(manifest, {
101
+ channel: 'stable',
102
+ }).version
71
103
  const cliVersion = getCliVersion()
72
104
  if (cliVersion === '0.0.0') {
73
105
  throw new Error('could not resolve the installed rnx CLI version')
74
106
  }
75
107
 
76
- let repositoryPlan: RepositorySetupPlan | null = null
77
- if (repository && packageRoot) {
78
- repositoryPlan = planRepositorySetup({
79
- appDir: packageRoot,
80
- cliVersion,
81
- })
108
+ let runtimeInstalled = false
109
+ if (!options.dryRun) {
110
+ const activeRuntime = readActiveRuntime()
111
+ const install = () =>
112
+ sootsimRuntime.install({
113
+ version: runtimeVersion,
114
+ channel: 'stable',
115
+ setActive: true,
116
+ protectVersions: activeRuntime ? [activeRuntime] : [],
117
+ })
118
+ const result = options.json
119
+ ? await install()
120
+ : await withSpinner(`Preparing runtime ${runtimeVersion}`, install)
121
+ runtimeInstalled = result.installed
82
122
  }
83
123
 
84
- printIntro(options)
85
- printMachineSection(options, serviceStatus, service, runtimeVersion)
86
- printRepositorySection(options, packageRoot, repositoryPlan)
87
- printPrivacySection(options, productAnalytics)
124
+ let serviceInstalled = serviceStatus.installed
125
+ if (service && !serviceStatus.installed && !options.dryRun) {
126
+ if (options.json) {
127
+ await daemonInstall({ port: DEFAULT_SOOTSIM_BRIDGE_PORT, force: false })
128
+ } else {
129
+ console.log(`\n ${accent('◆')} ${bold('Enabling the background service')}`)
130
+ await daemonInstall({ port: DEFAULT_SOOTSIM_BRIDGE_PORT, force: false })
131
+ }
132
+ serviceInstalled = true
133
+ }
88
134
 
89
- let installed = false
90
- if (!options.dryRun) {
91
- const activeRuntime = readActiveRuntime()
92
- const result = await sootsimRuntime.install({
93
- version: runtimeVersion,
94
- channel: 'stable',
95
- setActive: true,
96
- protectVersions: activeRuntime ? [activeRuntime] : [],
97
- })
98
- installed = result.installed
135
+ if (simulatorDriver === 'electron' && !options.dryRun && !findDesktopCompanion()) {
136
+ await runInstallDesktop(['--yes'])
137
+ }
138
+
139
+ if (launchConnectRN) {
140
+ console.log(`\n ${accent('')} Opening ConnectRN`)
141
+ await runOpenCommand([
142
+ 'ConnectRN',
143
+ '--driver',
144
+ simulatorDriver,
145
+ '--no-describe',
146
+ '--quiet',
147
+ ])
99
148
  }
100
149
 
101
- if (repositoryPlan) {
150
+ const productAnalytics = await decidePrivacy(options, privacy)
151
+ const crashReports = false
152
+
153
+ const repositoryPlan = packageRoot
154
+ ? planRepositorySetup({ appDir: packageRoot, cliVersion })
155
+ : null
156
+ const repository = await decideRepository(options, repositoryPlan)
157
+ if (repository && repositoryPlan) {
158
+ if (!options.json) {
159
+ for (const operation of formatRepositoryOperations(repositoryPlan)) {
160
+ console.log(` ${muted(options.dryRun ? 'Would run' : 'Running')} ${operation}`)
161
+ }
162
+ }
102
163
  await applyRepositorySetup(repositoryPlan, {
103
164
  dryRun: options.dryRun,
104
165
  json: options.json,
105
166
  })
106
167
  }
107
168
 
108
- let serviceInstalled = serviceStatus.installed
109
- if (service && !serviceStatus.installed && !options.dryRun) {
110
- await daemonInstall({ port: DEFAULT_SOOTSIM_BRIDGE_PORT, force: false })
111
- serviceInstalled = true
112
- }
113
-
114
169
  if (!options.dryRun) {
115
170
  writePrivacyPreferences({ productAnalytics, crashReports })
171
+ writeOnboardingPreferences(simulatorDriver)
116
172
  }
117
173
 
118
- const scan = packageRoot ? scanPackage(packageRoot) : null
119
- printCompatibilitySection(options, scan)
120
-
121
174
  const result: SetupResult = {
122
175
  runtimeVersion,
123
176
  serviceInstalled,
124
- repositoryPrepared: repositoryPlan !== null,
177
+ repositoryPrepared: repository,
178
+ simulatorDriver,
125
179
  }
126
- const cache = runtimeCacheMetadata(runtimeVersion)
127
180
  if (options.json) {
128
181
  console.log(
129
182
  JSON.stringify(
130
183
  {
131
184
  ...result,
132
- runtimeInstalled: options.dryRun ? false : installed,
185
+ runtimeInstalled,
133
186
  dryRun: options.dryRun,
134
- cache,
135
- repositoryOperations: repositoryPlan
136
- ? formatRepositoryOperations(repositoryPlan)
137
- : [],
187
+ cache: runtimeCacheMetadata(runtimeVersion),
188
+ repositoryOperations:
189
+ repository && repositoryPlan
190
+ ? formatRepositoryOperations(repositoryPlan)
191
+ : [],
138
192
  },
139
193
  null,
140
194
  2,
141
195
  ),
142
196
  )
143
197
  } else {
144
- printReadySection(repositoryPlan)
198
+ console.log(
199
+ `\n ${success('✓')} ${launchConnectRN ? 'ConnectRN is open. ' : ''}rnx is ready.`,
200
+ )
145
201
  }
146
202
 
147
203
  return result
@@ -157,20 +213,30 @@ function parseSetupOptions(args: string[]): SetupOptions {
157
213
  if (appIndex >= 0 && (!appArg || appArg.startsWith('-'))) {
158
214
  throw new Error('--app requires a directory')
159
215
  }
160
- const service = readBooleanFlag(args, '--service', '--no-service')
161
- const repository = readBooleanFlag(args, '--repo', '--no-repo')
162
216
  return {
163
217
  appDir: path.resolve(appArg ?? process.cwd()),
164
218
  ci: args.includes('--ci') || isAgentEnv(),
165
219
  dryRun: args.includes('--dry-run'),
166
220
  json: args.includes('--json'),
167
221
  yes: args.includes('--yes') || args.includes('-y'),
168
- service,
169
- repository,
222
+ service: readBooleanFlag(args, '--service', '--no-service'),
223
+ repository: readBooleanFlag(args, '--repo', '--no-repo'),
170
224
  productAnalytics: readOnOffFlag(args, '--analytics'),
225
+ simulatorDriver: readSimulatorDriverFlag(args),
171
226
  }
172
227
  }
173
228
 
229
+ function readSimulatorDriverFlag(args: string[]): SimulatorDriverPreference | undefined {
230
+ const desktop = args.includes('--desktop')
231
+ const browser = args.includes('--browser')
232
+ if (desktop && browser) {
233
+ throw new Error('--desktop and --browser cannot be used together')
234
+ }
235
+ if (desktop) return 'electron'
236
+ if (browser) return 'playwright'
237
+ return undefined
238
+ }
239
+
174
240
  function readBooleanFlag(
175
241
  args: string[],
176
242
  positive: string,
@@ -202,26 +268,59 @@ async function decideService(
202
268
  if (options.service === true) throw new Error('--ci cannot install a machine service')
203
269
  return false
204
270
  }
205
- if (!status.supported) return false
206
- if (status.installed && options.service === undefined) return true
271
+ if (!status.supported) {
272
+ if (!options.json) {
273
+ printPromptBlock('Background service', [
274
+ 'The persistent service is currently available on macOS and Linux.',
275
+ 'rnx will start the bridge when needed on this platform.',
276
+ ])
277
+ }
278
+ return false
279
+ }
280
+ if (status.installed && options.service === undefined) {
281
+ if (!options.json) console.log(` ${success('✓')} Background service is ready`)
282
+ return true
283
+ }
207
284
  if (options.service !== undefined) return options.service
208
285
  if (options.yes) return true
209
286
  requireInteractive(options, '--service or --no-service')
210
- return confirm('Install the background service and shared managed runtime?', true)
287
+ printPromptBlock('Background service', [
288
+ 'Keeps the local bridge and runtime ready between commands.',
289
+ 'Agent inspection, interaction, and test commands start much faster.',
290
+ ])
291
+ return confirm('Enable the background service?', true)
211
292
  }
212
293
 
213
- async function decideRepository(
294
+ async function decideSimulatorDriver(
214
295
  options: SetupOptions,
215
- packageRoot: string | null,
216
- ): Promise<boolean> {
217
- if (!packageRoot) {
218
- if (options.repository === true) throw new Error('--repo requires a package.json')
219
- return false
220
- }
221
- if (options.repository !== undefined) return options.repository
222
- if (options.yes) return true
223
- requireInteractive(options, '--repo or --no-repo')
224
- return confirm(`Prepare ${packageRoot} for reproducible rnx runs?`, true)
296
+ ): Promise<SimulatorDriverPreference> {
297
+ if (options.simulatorDriver) return options.simulatorDriver
298
+ const current = readSimulatorDriverPreference()
299
+ if (current) return current
300
+ if (options.ci || options.yes) return 'playwright'
301
+ requireInteractive(options, '--desktop or --browser')
302
+ const desktopDescription =
303
+ process.platform === 'darwin'
304
+ ? 'Desktop downloads a DMG and installs rnx.app.'
305
+ : process.platform === 'win32'
306
+ ? 'Desktop downloads and runs the rnx installer.'
307
+ : 'Desktop downloads the matching rnx AppImage.'
308
+ const desktopChoice =
309
+ process.platform === 'darwin'
310
+ ? 'Desktop app Installs rnx.app from a DMG'
311
+ : process.platform === 'win32'
312
+ ? 'Desktop app Runs the rnx installer'
313
+ : 'Desktop app Installs the matching AppImage'
314
+ printPromptBlock('Simulator window', [
315
+ 'Browser opens immediately in an isolated Chrome window.',
316
+ desktopDescription,
317
+ 'The Desktop app opens in a dedicated native window.',
318
+ ])
319
+ const choice = await select('Where should rnx open?', [
320
+ 'Browser (recommended) No additional download',
321
+ desktopChoice,
322
+ ])
323
+ return choice === 0 ? 'playwright' : 'electron'
225
324
  }
226
325
 
227
326
  async function decidePrivacy(
@@ -231,10 +330,32 @@ async function decidePrivacy(
231
330
  if (options.productAnalytics !== undefined) return options.productAnalytics
232
331
  if (current.configured) return current.productAnalytics
233
332
  if (options.yes || options.ci || !process.stdin.isTTY) return false
234
- return confirm(
235
- 'Share anonymous package incompatibilities and error classes to improve rnx?',
236
- false,
237
- )
333
+ printPromptBlock('Private by default', [
334
+ 'Diagnostics stay off unless you opt in.',
335
+ 'Reports can contain package compatibility or an error class.',
336
+ 'They never contain your code or app data.',
337
+ ])
338
+ return confirm('Share bounded diagnostics to improve rnx?', false)
339
+ }
340
+
341
+ async function decideRepository(
342
+ options: SetupOptions,
343
+ plan: RepositorySetupPlan | null,
344
+ ): Promise<boolean> {
345
+ if (!plan) {
346
+ if (options.repository === true) {
347
+ throw new Error('--repo requires a package.json inside a Git repository')
348
+ }
349
+ return false
350
+ }
351
+ if (plan.operations.length === 0) return true
352
+ if (options.repository !== undefined) return options.repository
353
+ if (options.yes || options.ci || !process.stdin.isTTY) return false
354
+ printPromptBlock('Share rnx with your team', [
355
+ 'rnx already works here with no project setup.',
356
+ 'This only adds an exact rnxsim dev dependency and an rnx script for teammates.',
357
+ ])
358
+ return confirm(`Add rnx to ${path.basename(plan.appDir)}?`, false)
238
359
  }
239
360
 
240
361
  function requireInteractive(options: SetupOptions, flags: string): void {
@@ -242,16 +363,6 @@ function requireInteractive(options: SetupOptions, flags: string): void {
242
363
  throw new Error(`non-interactive setup requires ${flags}, or --yes to accept defaults`)
243
364
  }
244
365
 
245
- function scanPackage(packageRoot: string): ScanResult {
246
- const parsed: unknown = JSON.parse(
247
- fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf8'),
248
- )
249
- if (!parsed || typeof parsed !== 'object') {
250
- throw new Error('package.json must contain an object')
251
- }
252
- return scanDeps(parsed)
253
- }
254
-
255
366
  function runtimeCacheMetadata(runtimeVersion: string) {
256
367
  const platform = os.platform()
257
368
  const arch = os.arch()
@@ -264,89 +375,16 @@ function runtimeCacheMetadata(runtimeVersion: string) {
264
375
 
265
376
  function printIntro(options: SetupOptions): void {
266
377
  if (options.json) return
267
- console.log(`\n rnx setup`)
268
- }
269
-
270
- function printMachineSection(
271
- options: SetupOptions,
272
- status: ReturnType<typeof getDaemonServiceStatus>,
273
- service: boolean,
274
- runtimeVersion: string,
275
- ): void {
276
- if (options.json) return
277
- console.log(`\n 1. machine`)
278
- console.log(` stable runtime: ${runtimeVersion}`)
279
- console.log(
280
- ` background service: ${status.supported ? (service ? 'install' : 'skip') : 'unsupported on this platform'}`,
281
- )
282
- }
283
-
284
- function printRepositorySection(
285
- options: SetupOptions,
286
- packageRoot: string | null,
287
- plan: RepositorySetupPlan | null,
288
- ): void {
289
- if (options.json) return
290
- console.log(`\n 2. repository`)
291
- if (!packageRoot) {
292
- console.log(` no package.json found; repository preparation skipped`)
293
- return
294
- }
295
- if (!plan) {
296
- console.log(` skipped for ${packageRoot}`)
297
- return
298
- }
299
- console.log(` package manager: ${detectPreferredPackageManager(packageRoot)}`)
300
- for (const operation of formatRepositoryOperations(plan)) {
301
- console.log(` ${options.dryRun ? 'would run' : 'run'}: ${operation}`)
302
- }
303
- }
304
-
305
- function printPrivacySection(options: SetupOptions, productAnalytics: boolean): void {
306
- if (options.json) return
307
- console.log(`\n 3. privacy`)
308
- console.log(` bounded diagnostics: ${productAnalytics ? 'on' : 'off'}`)
309
- console.log(` package name/version/unsupported API or error class only`)
310
- console.log(
311
- ` source, paths, project names, app content, identity, and command usage are never included`,
312
- )
313
- }
314
-
315
- function printCompatibilitySection(options: SetupOptions, scan: ScanResult | null): void {
316
- if (options.json) return
317
- console.log(`\n 4. compatibility`)
318
- if (!scan) {
319
- console.log(` no package.json found; run \`rnx compat\` inside an app later`)
320
- return
321
- }
322
- console.log(` ${formatScanCounts(scan)}`)
323
- for (const line of formatDetailedScanLines(scan)) console.log(` ${line}`)
324
- console.log(` ${COMPAT_RESULT_CAVEAT}`)
325
- }
326
-
327
- function printReadySection(repository: RepositorySetupPlan | null): void {
328
- console.log(`\n 5. ready`)
329
- if (repository) {
330
- const command =
331
- repository.packageManager === 'bun'
332
- ? 'bun run sootsim'
333
- : `${repository.packageManager} run rnx`
334
- console.log(` start your app normally, then ${command} -- <port>`)
335
- } else {
336
- console.log(` start your app normally, then rnx open <port>`)
337
- }
338
- console.log(` useful next commands: rnx describe; rnx get errors; rnx compat`)
339
- console.log(
340
- ` install agent skills with \`rnx skill install\`, then start a new session`,
341
- )
342
- console.log(
343
- ` if a concrete rnx seam is missing or broken, preview \`rnx report-issue\` and confirm separately`,
344
- )
378
+ printBrand('Run any React Native app in a local simulator. No project setup required.')
345
379
  }
346
380
 
347
381
  function printHelp(): void {
348
382
  console.log(`
349
- rnx setup configure a machine and optionally prepare the current repository
383
+ rnx setup - configure optional local conveniences
384
+
385
+ rnx works without project setup. This command can enable the faster background
386
+ service, choose Browser or Desktop, configure private diagnostics, and optionally
387
+ add an exact rnxsim dependency plus an rnx script for teammates.
350
388
 
351
389
  usage:
352
390
  rnx setup
@@ -354,16 +392,17 @@ usage:
354
392
  rnx setup --ci --yes --json
355
393
  rnx setup --dry-run --yes
356
394
 
357
- decisions:
395
+ choices:
358
396
  --service | --no-service
359
- --repo | --no-repo
397
+ --desktop | --browser
360
398
  --analytics on|off
399
+ --repo | --no-repo
361
400
 
362
401
  automation:
363
- --yes accept setup defaults; diagnostics remain off unless explicitly enabled
402
+ --yes accept recommended machine defaults; diagnostics and repo changes stay off
364
403
  --ci never prompt or install a background service
365
- --json emit runtime/cache metadata as JSON
366
- --dry-run print exact repository operations without changing the machine
367
- --app DIR use DIR as the repository search start
404
+ --json emit runtime and cache metadata as JSON
405
+ --dry-run show choices and repository operations without changing anything
406
+ --app DIR inspect DIR for an optional repository installation
368
407
  `)
369
408
  }
@@ -1,66 +1,17 @@
1
- import { scanDevServers, type DiscoveredServer } from '../../scripts/dev-server-scanner'
2
- import { DEFAULT_SOOTSIM_BRIDGE_PORT } from '../../src/bridge-constants'
3
- import { readPrivacyPreferences } from '../../src/home-paths'
1
+ import { isOnboardingComplete } from '../../src/home-paths'
4
2
  import { canPromptForPrivacyChoice } from '../privacy'
5
- import { select } from '../prompt'
6
- import { createBridge } from '../ws-bridge'
3
+ import { printBrand } from '../ui'
7
4
  import { runOpenCommand } from './control'
8
-
9
- function serverLabel(server: DiscoveredServer): string {
10
- return `${server.projectName?.trim() || server.framework} on :${server.port}`
11
- }
5
+ import { runSetup } from './setup'
12
6
 
13
7
  export async function runWelcome(): Promise<number> {
14
- const privacy = readPrivacyPreferences()
15
- console.log('\n welcome to rnx')
16
- console.log(
17
- ` diagnostics: ${privacy.configured && privacy.productAnalytics ? 'bounded opt-in on' : 'off until you opt in'}`,
18
- )
19
- console.log(' looking for a running React Native app…')
20
-
21
- const discovered = await scanDevServers({
22
- excludePorts: [DEFAULT_SOOTSIM_BRIDGE_PORT],
23
- })
24
- const ordered = [...discovered].sort((a, b) => a.port - b.port)
25
- if (ordered.length === 0) {
26
- console.error(' no running React Native development server found.')
27
- console.error(' start your app normally, then run `npx rnxsim` again.')
28
- return 1
8
+ if (!isOnboardingComplete() && canPromptForPrivacyChoice()) {
9
+ await runSetup([], { launchConnectRN: true })
10
+ return 0
29
11
  }
30
- if (ordered.length > 1 && !canPromptForPrivacyChoice()) {
31
- console.error(' multiple React Native development servers found.')
32
- for (const server of ordered) {
33
- console.error(` ${serverLabel(server)}: rnx open ${server.port}`)
34
- }
35
- console.error(' choose one explicitly; automated runs do not guess.')
36
- return 1
37
- }
38
- const selectedIndex =
39
- ordered.length === 1
40
- ? 0
41
- : await select('which running app should rnx open?', ordered.map(serverLabel))
42
- const selected = ordered[selectedIndex]
43
- if (!selected) throw new Error('rnx app selection returned an invalid choice')
44
12
 
45
- console.log(` found ${serverLabel(selected)} · opening simulator…`)
46
- const bridgePort = await runOpenCommand([selected.bundleUrl])
47
- if (bridgePort !== undefined) {
48
- const { runInspect } = await import('./inspect')
49
- await runInspect(['wait', 'ready', '--max-ms', '120000'], { port: bridgePort })
50
- const bridge = createBridge(bridgePort, { commandTimeoutMs: 45_000 })
51
- try {
52
- const frame: unknown = await bridge.send({ type: 'screenshot' })
53
- const pngPrefix = 'data:image/png;base64,'
54
- if (
55
- typeof frame !== 'string' ||
56
- !frame.startsWith(pngPrefix) ||
57
- frame.length <= pngPrefix.length
58
- ) {
59
- throw new Error('the simulator connected but did not produce a visible app frame')
60
- }
61
- } finally {
62
- bridge.close()
63
- }
64
- }
13
+ printBrand('Run any React Native app in a local simulator.')
14
+ console.log(`\n Opening ConnectRN`)
15
+ await runOpenCommand(['ConnectRN', '--no-describe', '--quiet'])
65
16
  return 0
66
17
  }