rnxsim 0.1.380 → 0.1.382

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 (138) hide show
  1. package/README.md +43 -26
  2. package/cli/app-fonts.ts +1 -1
  3. package/cli/bin.ts +118 -26
  4. package/cli/bridge-diagnostics.ts +1 -1
  5. package/cli/bridge-flow-runner.ts +9 -9
  6. package/cli/browser-evals.ts +3 -3
  7. package/cli/cli-update.ts +261 -0
  8. package/cli/cloud-client.ts +407 -0
  9. package/cli/cloud-dispatch.ts +165 -0
  10. package/cli/cloud-session.ts +156 -0
  11. package/cli/command-contract.ts +2 -2
  12. package/cli/command-registry.ts +9 -0
  13. package/cli/commands/agent-wrapper.ts +4 -4
  14. package/cli/commands/agent.ts +3 -3
  15. package/cli/commands/box/checkout-plane.ts +16 -8
  16. package/cli/commands/box/rnx-command.ts +2 -2
  17. package/cli/commands/box/search-command.ts +2 -2
  18. package/cli/commands/box.ts +123 -70
  19. package/cli/commands/config.ts +3 -2
  20. package/cli/commands/control.ts +6 -6
  21. package/cli/commands/cpu-profile.ts +2 -2
  22. package/cli/commands/daemon.ts +7 -7
  23. package/cli/commands/debug.ts +2 -2
  24. package/cli/commands/detox.ts +7 -7
  25. package/cli/commands/diagnose.ts +1 -1
  26. package/cli/commands/electron.ts +1 -1
  27. package/cli/commands/film.ts +1 -1
  28. package/cli/commands/flow.ts +4 -4
  29. package/cli/commands/inspect/core.ts +58 -7
  30. package/cli/commands/inspect/env.ts +5 -3
  31. package/cli/commands/inspect/shared.ts +2 -1
  32. package/cli/commands/inspect/wait-event.ts +1 -1
  33. package/cli/commands/inspect/wait-ready.ts +15 -5
  34. package/cli/commands/inspect.ts +31 -9
  35. package/cli/commands/install-desktop.ts +2 -2
  36. package/cli/commands/maestro.ts +3 -3
  37. package/cli/commands/permissions.ts +1 -1
  38. package/cli/commands/platform.ts +91 -0
  39. package/cli/commands/record.ts +3 -3
  40. package/cli/commands/report-issue.ts +1 -1
  41. package/cli/commands/runtime.ts +1 -1
  42. package/cli/commands/screenshot-mode.ts +1 -1
  43. package/cli/commands/screenshot.ts +25 -0
  44. package/cli/commands/serve.ts +1 -1
  45. package/cli/commands/skills.ts +1 -1
  46. package/cli/commands/timeline.ts +1 -1
  47. package/cli/commands/upgrade.ts +8 -34
  48. package/cli/commands/upload.ts +3 -3
  49. package/cli/commands/version.ts +14 -3
  50. package/cli/commands/what-happened.ts +2 -2
  51. package/cli/current-sim.ts +43 -30
  52. package/cli/desktop-companion.ts +1 -1
  53. package/cli/drivers/electron.ts +1 -1
  54. package/cli/drivers/playwright.ts +3 -3
  55. package/cli/drivers/types.ts +4 -4
  56. package/cli/flow-file.ts +3 -3
  57. package/cli/help-core.ts +18 -11
  58. package/cli/hidden-runtime-alias.ts +1 -1
  59. package/cli/hints.ts +2 -2
  60. package/cli/maestro-js.ts +2 -2
  61. package/cli/outbound-endpoints.ts +40 -6
  62. package/cli/parse-args.ts +1 -1
  63. package/cli/registry.ts +1 -1
  64. package/cli/resolve-assets.ts +2 -2
  65. package/cli/secure-file.ts +31 -0
  66. package/cli/shell-init.ts +98 -0
  67. package/cli/ws-bridge.ts +18 -8
  68. package/detox/expectations.ts +1 -1
  69. package/detox/gestures.ts +3 -3
  70. package/detox/index.ts +3 -3
  71. package/dist-lib/agent-daemon-client.cjs +1 -1
  72. package/dist-lib/agent-events.cjs +1 -1
  73. package/dist-lib/agent-identity.cjs +1 -1
  74. package/dist-lib/agent-sessions.cjs +1 -1
  75. package/dist-lib/attached-projects.cjs +1 -1
  76. package/dist-lib/auth/shared-session.cjs +1 -1
  77. package/dist-lib/backend-origin.cjs +1 -1
  78. package/dist-lib/beta.cjs +1 -1
  79. package/dist-lib/beta.mjs +1 -1
  80. package/dist-lib/bridge-constants.cjs +1 -1
  81. package/dist-lib/bridge-contract-input.cjs +15 -8
  82. package/dist-lib/bridge-contract-input.mjs +15 -8
  83. package/dist-lib/bridge-contract.cjs +1 -1
  84. package/dist-lib/bridge-contract.mjs +1 -1
  85. package/dist-lib/capture-contract.cjs +107 -0
  86. package/dist-lib/capture-contract.mjs +80 -0
  87. package/dist-lib/cli-constants.cjs +1 -1
  88. package/dist-lib/cloud-contract.cjs +32 -0
  89. package/dist-lib/cloud-contract.mjs +7 -0
  90. package/dist-lib/config.cjs +1 -1
  91. package/dist-lib/detox/index.cjs +4 -4
  92. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  93. package/dist-lib/home-paths.cjs +9 -1
  94. package/dist-lib/host/bridge-host.cjs +15 -10
  95. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  96. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  97. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  98. package/dist-lib/host/replacement-module-handler.cjs +1 -1
  99. package/dist-lib/host/websocket-proxy.cjs +1 -1
  100. package/dist-lib/index.cjs +248 -73
  101. package/dist-lib/jump-to-source-babel.cjs +1 -1
  102. package/dist-lib/menu.cjs +1 -1
  103. package/dist-lib/menu.mjs +1 -1
  104. package/dist-lib/metro-production-bundle.cjs +1 -1
  105. package/dist-lib/metro-production-bundle.mjs +1 -1
  106. package/dist-lib/metro.cjs +1 -1
  107. package/dist-lib/profiles.cjs +1 -1
  108. package/dist-lib/public-brand.cjs +1 -1
  109. package/dist-lib/react-native-host-modules.cjs +1 -1
  110. package/dist-lib/react-native-host-modules.mjs +1 -1
  111. package/dist-lib/render-mode.cjs +1 -1
  112. package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
  113. package/dist-lib/sdk.cjs +44 -6
  114. package/dist-lib/sdk.mjs +44 -6
  115. package/dist-lib/skills.cjs +435 -155
  116. package/dist-lib/vite.cjs +1 -1
  117. package/package.json +21 -5
  118. package/scripts/dev-server-scanner.ts +3 -3
  119. package/skills/contrast/SKILL.md +1 -1
  120. package/skills/rnx-perf/SKILL.md +68 -52
  121. package/src/app-url.ts +2 -3
  122. package/src/bridge-contract-input.ts +17 -6
  123. package/src/bridge-contract.ts +25 -4
  124. package/src/capture-contract.ts +423 -0
  125. package/src/cloud-contract.ts +15 -0
  126. package/src/home-paths.ts +9 -3
  127. package/src/host/agent-host.ts +1 -1
  128. package/src/host/bridge-host.ts +13 -11
  129. package/src/host/plane-host.ts +2 -2
  130. package/src/index.ts +21 -0
  131. package/src/native-dev-bundle-url.ts +2 -2
  132. package/src/screenshots/orchestrate.ts +1 -1
  133. package/src/screenshots/org-deck.ts +1 -1
  134. package/src/settings-bundle.ts +1 -1
  135. package/src/sim-client.ts +4 -0
  136. package/src/vite-plugin-one.ts +2 -2
  137. package/src/vite-plugin.ts +1 -1
  138. package/cli/commands/box/cloud-box.ts +0 -294
package/README.md CHANGED
@@ -1,21 +1,33 @@
1
1
  # rnx
2
2
 
3
- The public, publishable face of rnx: a CLI, two pairs of bundler plugins, a
4
- Detox/Maestro test surface, and a skills registry for driving a browser-native
5
- React Native simulator.
3
+ The source owner for the standalone `rnx` executable and the public `rnxsim`
4
+ integration library used to drive a browser-native React Native simulator.
6
5
 
7
6
  ## Overview
8
7
 
9
- `rnxsim` is everything you install to *talk to* an rnx instance — it
10
- contains **no rendering code**. The canvas engine (CanvasKit renderer, Yoga
11
- layout, iOS/Android shell chrome, Electron shell) lives in the private
12
- `sootsim-engine` workspace package and is shipped at runtime as a versioned
13
- tarball, fetched from the contrast.dev CDN and unpacked into `~/.rnx`.
14
-
15
- What this package gives you:
16
-
17
- - the `rnx` CLI the primary debugging and automation surface (inspect the
18
- UI tree, tap elements, capture flows, screenshot, record, run agents)
8
+ The CLI and library are separate public artifacts. The recommended installer
9
+ downloads one Bun-compiled `rnx` executable that needs no Node.js, npm, or Bun
10
+ installation. The `rnxsim` npm package contains bundler plugins, testing
11
+ drivers, and library exports, with no command-line binary.
12
+
13
+ The executable updates itself with `rnx upgrade`. Successful interactive human
14
+ commands refresh a cached stable CLI version in a detached process at most once
15
+ every 20 hours. A newer version produces one short notice on stderr, once per
16
+ version and no more than weekly. CI, agents, JSON output, pipes, and
17
+ noninteractive commands stay silent. Set `RNX_UPDATE_CHECK=off` to disable the
18
+ check and notice; `rnx version` checks explicitly, while `rnx --version` remains
19
+ offline.
20
+
21
+ Neither artifact contains rendering code. The canvas engine (CanvasKit
22
+ renderer, Yoga layout, iOS/Android shell chrome, Electron shell) lives in the
23
+ private `sootsim-engine` workspace package and is shipped at runtime as a
24
+ versioned tarball, fetched from the contrast.dev CDN and unpacked into `~/.rnx`.
25
+
26
+ What this workspace gives you:
27
+
28
+ - the standalone `rnx` CLI source and binary build, which provides the primary
29
+ debugging and automation surface (inspect the UI tree, tap elements, capture
30
+ flows, screenshot, record, run agents)
19
31
  - `rnxsim/vite` and `rnxsim/metro` — bundler plugins that serve the installed
20
32
  engine runtime from your own dev server
21
33
  - `rnxsim/jump-to-source-babel` — an optional Babel plugin that annotates JSX
@@ -33,14 +45,14 @@ chrome, gesture + three-button nav, system UI/window metrics with cutout-safe
33
45
  insets) but is not full parity until the Android launch gates in the Contrast
34
46
  repo plan pass.
35
47
 
36
- ## What's in this package (and what's not)
48
+ ## What's in each distribution
37
49
 
38
- | in `rnxsim` | in `sootsim-engine` (private, not published) |
39
- | --- | --- |
40
- | CLI, bridge client, drivers | CanvasKit renderer, Yoga layout |
41
- | bridge daemon host (`SootSimBridgeHost`) | iOS/Android shell chrome, home grid, app switcher |
42
- | vite/metro plugins, RN resolver, native stubs | Electron shell, wasm |
43
- | Detox/Maestro runners, screenshots, skills | the actual pixels |
50
+ | standalone `rnx` executable | `rnxsim` npm package | private engine runtime |
51
+ | --- | --- | --- |
52
+ | CLI commands and bridge client | Vite and Metro plugins | CanvasKit renderer and Yoga layout |
53
+ | bridge daemon host | SDK and bridge contracts | iOS/Android shell chrome, home grid, app switcher |
54
+ | runtime, browser, and desktop launchers | Detox driver and Jest preset | Electron shell and wasm |
55
+ | Maestro, screenshots, recording, skills | native-resolution helpers and skills registry | the actual pixels |
44
56
 
45
57
  The engine is delivered at runtime, never bundled into this package. A
46
58
  committed-but-unbuilt engine fix has no effect on a running sim until the
@@ -197,6 +209,7 @@ bridge always addresses the **shell worker**, never an undifferentiated sim.
197
209
  | AgentHost + sessions | Agent-routing extension of the host: owns the single FIFO reader per agent session and fans `agent:event`/`session-status` pushes to every WS subscriber. Backed by agent-sessions + attached-projects stores. | `src/host/agent-host.ts`, `src/agent-sessions.ts`, `src/attached-projects.ts`, `src/agent-daemon-client.ts` |
198
210
  | host proxies + dev-server scan | Host-side helpers so a cross-origin tenant worker can reach local dev servers and same-origin APIs: `/__server-scan` discovers running metro/expo/vxrn/one servers; fetch + websocket proxies relay guest-app network through the daemon. | `src/host/fetch-proxy-handler.ts`, `src/host/websocket-proxy.ts`, `scripts/dev-server-scanner.ts`, `src/dev-bundle-resolution.ts` |
199
211
  | runtime delivery + rnx home | Versioned engine runtime management. The shared machinery owns manifest fetch, sha256 verification, channels, and auto-update. rnx binds it to `https://contrast.dev` and `~/.rnx/runtimes/<version>`. A repo can select `runtimeVersion` without changing the machine default. | `src/runtime-delivery.ts`, `packages/contrast-runtime-delivery/`, `src/home-paths.ts`, `src/runtime-assets.ts` |
212
+ | standalone CLI delivery | Cached stable-version checks, bounded interactive notices, and verified atomic executable replacement. | `cli/cli-update.ts`, `cli/commands/upgrade.ts`, `cli/commands/version.ts`, `src/cli-version.ts` |
200
213
  | `rnxsim/vite` + `rnxsim/metro` (runtime serving) | Published bundler plugins. Both serve the installed engine runtime at `/__soot/` from `~/.rnx/runtimes/<version>` so an app's own dev server can host the sim shell; either plugin can opt into a named Electron window. | `src/vite-plugin-one.ts`, `src/metro-plugin.ts`, `src/runtime-assets.ts` |
201
214
  | `sootsim()` vite resolver plugin (internal) | The large RN-resolution / native-stub vite plugin: aliases `react-native` to the engine shim, native packages to `@sootsim/compat` stubs, applies worklets/babel transforms, and instantiates `SootSimBridgeHost` in its dev server. Distinct from the published `rnxsim/vite` export. | `src/vite-plugin.ts`, `src/worklets-babel.ts`, `packages/compat/src/stub-manifest.ts` |
202
215
  | skills registry | rnx agent skills (`rnx-setup`, `rnx-debug`, `rnx-test`, `rnx-visual`, and the generated `contrast` index) installable with `rnx skill`. | `skills/*.md`, `src/skills/registry.ts`, `src/skills/types.ts` |
@@ -244,10 +257,13 @@ window for a selected device (same device list as the `Window >` menu).
244
257
 
245
258
  ### Privacy
246
259
 
247
- rnx does not phone home by default. On an unsigned-in first run, its only
248
- internet request is the runtime manifest/archive it is about to execute. The
249
- first human run asks once, defaults to no, and never prompts in a non-TTY shell,
250
- CI, or an agent. An automated first run is declined for that process without
260
+ rnx sends no analytics, crash report, command event, app data, project data, or
261
+ identity by default. An unsigned-in interactive run may request the runtime it
262
+ is about to execute and the stable standalone CLI version document. The latter
263
+ runs after the command in a detached process at most once every 20 hours and can
264
+ be disabled with `RNX_UPDATE_CHECK=off`. The first human run asks once about
265
+ bounded diagnostics, defaults to no, and never prompts in a non-TTY shell, CI,
266
+ or an agent. An automated first run is declined for that process without
251
267
  consuming the later human choice. Legacy browser and Electron telemetry senders
252
268
  are removed rather than being enabled by this consent.
253
269
 
@@ -515,12 +531,12 @@ frame for quick art-direction passes.
515
531
  `<skill-name>/SKILL.md` layout used by Codex and Claude Code. The bundled
516
532
  skills cover setup, debugging/perf/accessibility, testing, visual review, and
517
533
  the generated `contrast` command index. The published CLI registry and generated
518
- website docs both come from `packages/sootsim-skills/`:
534
+ website docs both come from `packages/rnx-skills/`:
519
535
 
520
536
  - `packages/sootsim/skills/contrast/SKILL.md` is generated from that registry
521
537
  - `src/features/site/docs/rnx/cli/*` is generated output, not hand-edited
522
538
 
523
- When command docs or examples drift, update `packages/sootsim-skills/` and run:
539
+ When command docs or examples drift, update `packages/rnx-skills/` and run:
524
540
 
525
541
  ```sh
526
542
  bun run generate:sootsim-docs
@@ -545,6 +561,7 @@ verifies its sha256, and unpacks it under `~/.rnx`:
545
561
  ├── profiles/ persistent named app storage, without browser caches
546
562
  ├── electron/userData/ desktop app storage, with bounded browser caches
547
563
  ├── daemon.json lockfile: pid, ports, active runtime, heartbeat
564
+ ├── cli-update.json cached standalone CLI version and notice state
548
565
  ├── automatic-cleanup-v1.json completed legacy cleanup generation
549
566
  └── config.json user prefs: update channel, cdn origin override
550
567
  ```
package/cli/app-fonts.ts CHANGED
@@ -124,7 +124,7 @@ export function discoverNativeLinkedAppFonts(opts: {
124
124
 
125
125
  // @react-native-vector-icons links fonts from its configured local font
126
126
  // directory and each installed icon package into the native app. those files
127
- // never enter the metro module graph, so SootSim must stage them through the
127
+ // never enter the metro module graph, so rnx must stage them through the
128
128
  // same native-linked font wire as expo-font config-plugin assets. Font Awesome
129
129
  // Pro installs its licensed faces under the local directory rather than
130
130
  // publishing them in the npm package.
package/cli/bin.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- // sootsim CLI entry point. the full command and verb surface lives in the
4
- // registry at `packages/sootsim-skills/src/cli/meta.ts` — help output,
3
+ // rnx CLI entry point. the full command and verb surface lives in the
4
+ // registry at `packages/rnx-skills/src/cli/meta.ts` — help output,
5
5
  // website docs, and agent skills all read from there. do not duplicate
6
- // command listings in this file; run `sootsim --help` for the canonical
6
+ // command listings in this file; run `rnx --help` for the canonical
7
7
  // surface.
8
8
 
9
9
  import { rnxPublicBrand } from '../src/public-brand'
@@ -11,6 +11,37 @@ import { parseArgs, TOP_LEVEL_RUNTIME_COMMANDS } from './parse-args'
11
11
  import { RnxExit } from './run-rnx'
12
12
  import { IS_STANDALONE } from './standalone'
13
13
 
14
+ if (process.argv[2] === '__shell-init') {
15
+ const { renderRnxShellInit } = await import('./shell-init')
16
+ try {
17
+ process.stdout.write(renderRnxShellInit(process.argv[3] ?? ''))
18
+ process.exit(0)
19
+ } catch (error) {
20
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`)
21
+ process.exit(1)
22
+ }
23
+ }
24
+
25
+ if (process.argv[2] === '__cloud-confirm') {
26
+ try {
27
+ const [{ confirmCloudSimulator }, { readCloudSession }] = await Promise.all([
28
+ import('./cloud-client'),
29
+ import('./cloud-session'),
30
+ ])
31
+ const session = readCloudSession()
32
+ if (!session) throw new Error('RNX cloud confirmation has no shell session')
33
+ await confirmCloudSimulator(session)
34
+ process.exit(0)
35
+ } catch (error) {
36
+ process.stderr.write(
37
+ `RNX cloud confirmation failed: ${
38
+ error instanceof Error ? error.message : String(error)
39
+ }\n`,
40
+ )
41
+ process.exit(1)
42
+ }
43
+ }
44
+
14
45
  // `./help` is loaded lazily (only on help paths) — it statically pulls the full
15
46
  // CLI registry renderers and engine settings, which would otherwise add ~400ms
16
47
  // of eager chunk load to every command, including hot-path reads like
@@ -25,7 +56,7 @@ import { IS_STANDALONE } from './standalone'
25
56
  // don't paper over real bugs.
26
57
  function handleTopLevelError(err: unknown): never {
27
58
  const msg = err instanceof Error ? err.message : String(err)
28
- // sootsim's known top-level errors are all single-line, narrow strings.
59
+ // rnx's known top-level errors are all single-line, narrow strings.
29
60
  // everything else gets the full stack so unexpected crashes still surface.
30
61
  const isKnownCliFailure =
31
62
  /^command timed out after \d+s$/.test(msg) ||
@@ -70,10 +101,24 @@ const isAutomaticCleanupWorker =
70
101
  parsed.command === 'cleanup' &&
71
102
  parsed.commandArgs.length === 1 &&
72
103
  parsed.commandArgs[0] === '--automatic-worker'
104
+ const isCliUpdateWorker =
105
+ parsed.command === 'upgrade' &&
106
+ parsed.commandArgs.length === 1 &&
107
+ parsed.commandArgs[0] === '--check-worker'
73
108
  const isBareWelcome = !parsed.command && parsed.commandArgs.length === 0
74
109
  const isReadOnlyInvocation =
75
110
  parsed.version || parsed.help || parsed.commandArgs.includes('--dry-run')
76
111
 
112
+ if (isCliUpdateWorker) {
113
+ if (IS_STANDALONE) {
114
+ try {
115
+ const { refreshCliUpdateCache } = await import('./cli-update')
116
+ await refreshCliUpdateCache()
117
+ } catch {}
118
+ }
119
+ process.exit(0)
120
+ }
121
+
77
122
  if (
78
123
  !isAutomaticCleanupWorker &&
79
124
  !isBareWelcome &&
@@ -105,6 +150,25 @@ if (!isReadOnlyInvocation && parsed.command !== 'serve' && parsed.command !== 'd
105
150
  }
106
151
 
107
152
  let automaticCleanupFinished = false
153
+ async function spawnDetachedSelf(
154
+ args: string[],
155
+ stderr: 'ignore' | 'inherit',
156
+ ): Promise<import('node:child_process').ChildProcess> {
157
+ const { spawn } = await import('node:child_process')
158
+ const options: import('node:child_process').SpawnOptions = {
159
+ detached: true,
160
+ stdio: ['ignore', 'ignore', stderr],
161
+ env: process.env,
162
+ }
163
+ const entry = process.argv[1]
164
+ const child = IS_STANDALONE
165
+ ? spawn(process.execPath, args, options)
166
+ : entry
167
+ ? spawn(process.execPath, [entry, ...args], options)
168
+ : spawn(rnxPublicBrand.commandName, args, options)
169
+ return child
170
+ }
171
+
108
172
  async function scheduleAutomaticCleanup(): Promise<void> {
109
173
  if (automaticCleanupFinished) return
110
174
  automaticCleanupFinished = true
@@ -122,26 +186,8 @@ async function scheduleAutomaticCleanup(): Promise<void> {
122
186
  const { shouldSkipAutomaticSootsimCleanup } = await import('../src/disk-cleanup')
123
187
  if (shouldSkipAutomaticSootsimCleanup()) return
124
188
 
125
- const { spawn } = await import('node:child_process')
126
189
  const workerArgs = ['cleanup', '--automatic-worker']
127
- const entry = process.argv[1]
128
- const child = IS_STANDALONE
129
- ? spawn(process.execPath, workerArgs, {
130
- detached: true,
131
- stdio: ['ignore', 'ignore', 'inherit'],
132
- env: process.env,
133
- })
134
- : entry
135
- ? spawn(process.execPath, [entry, ...workerArgs], {
136
- detached: true,
137
- stdio: ['ignore', 'ignore', 'inherit'],
138
- env: process.env,
139
- })
140
- : spawn(rnxPublicBrand.commandName, workerArgs, {
141
- detached: true,
142
- stdio: ['ignore', 'ignore', 'inherit'],
143
- env: process.env,
144
- })
190
+ const child = await spawnDetachedSelf(workerArgs, 'inherit')
145
191
  child.once('error', (error) => {
146
192
  process.stderr.write(
147
193
  ` ${rnxPublicBrand.name} automatic cleanup will retry on the next run: ${error.message}\n`,
@@ -157,9 +203,48 @@ async function scheduleAutomaticCleanup(): Promise<void> {
157
203
  }
158
204
  }
159
205
 
206
+ let automaticCliUpdateFinished = false
207
+ async function scheduleAutomaticCliUpdate(exitCode: number): Promise<void> {
208
+ if (automaticCliUpdateFinished) return
209
+ automaticCliUpdateFinished = true
210
+ if (!IS_STANDALONE) return
211
+ const {
212
+ claimAutomaticCliUpdateCheck,
213
+ shouldUseAutomaticCliUpdateCheck,
214
+ takeCliUpdateNotification,
215
+ } = await import('./cli-update')
216
+ if (
217
+ !shouldUseAutomaticCliUpdateCheck({
218
+ command: parsed.command,
219
+ commandArgs: parsed.commandArgs,
220
+ exitCode,
221
+ standalone: IS_STANDALONE,
222
+ stderrIsTTY: process.stderr.isTTY === true,
223
+ stdoutIsTTY: process.stdout.isTTY === true,
224
+ version: parsed.version,
225
+ })
226
+ ) {
227
+ return
228
+ }
229
+
230
+ const update = takeCliUpdateNotification()
231
+ if (update) {
232
+ process.stderr.write(
233
+ ` update: rnx v${update.currentVersion} → v${update.latestVersion} · run \`rnx upgrade\`\n`,
234
+ )
235
+ }
236
+ if (!claimAutomaticCliUpdateCheck()) return
237
+ try {
238
+ const child = await spawnDetachedSelf(['upgrade', '--check-worker'], 'ignore')
239
+ child.once('error', () => {})
240
+ child.unref()
241
+ } catch {}
242
+ }
243
+
160
244
  async function exitWithFlush(code: number): Promise<never> {
161
245
  const { flushCliTelemetry } = await import('./telemetry')
162
246
  await flushCliTelemetry()
247
+ await scheduleAutomaticCliUpdate(code)
163
248
  await scheduleAutomaticCleanup()
164
249
  process.exit(code)
165
250
  }
@@ -222,7 +307,7 @@ if (!parsed.command && parsed.commandArgs.length === 0) {
222
307
  }
223
308
 
224
309
  // route to command. an empty command at this point only happens if the
225
- // caller passed bare positional args without a verb (e.g. `sootsim -- foo`),
310
+ // caller passed bare positional args without a verb (e.g. `rnx -- foo`),
226
311
  // which used to fall through to the bundler-wrap behavior in `dev`. now that
227
312
  // `dev` is gone, treat it as "show help".
228
313
  const command = parsed.command ?? ''
@@ -280,7 +365,7 @@ if (
280
365
  // shell and perf subcommands are documented by their runtime dispatchers.
281
366
  // don't try to route through generated per-verb docs.
282
367
  } else if (isGroupingVerb && !subVerb) {
283
- // bare `sootsim <group> --help` — render from the registry.
368
+ // bare `rnx <group> --help` — render from the registry.
284
369
  const { printGroupHelp } = await import('./help')
285
370
  if (printGroupHelp(command)) await exitWithFlush(0)
286
371
  // fall through — runInspect shows the full help for anything the
@@ -296,6 +381,12 @@ if (
296
381
  }
297
382
  }
298
383
 
384
+ const { dispatchCloudBoundary } = await import('./cloud-dispatch')
385
+ const cloudBoundary = await dispatchCloudBoundary({
386
+ command,
387
+ args: commandArgsWithSim,
388
+ })
389
+ if (cloudBoundary.handled) await exitWithFlush(cloudBoundary.code)
299
390
  // the command modules stop by throwing RnxExit rather than ending the
300
391
  // process, because the same command has to run in a box shell and in
301
392
  // workerd, where process.exit() cancels the whole request. this is the
@@ -331,7 +422,7 @@ try {
331
422
  const { runMaestro } = await import('./commands/maestro')
332
423
  // Maestro forwards playback argv to the shared runner, so it must
333
424
  // receive the global `--sim`. passing the bare
334
- // parsed.commandArgs dropped an explicit `sootsim --sim <id> maestro
425
+ // parsed.commandArgs dropped an explicit `rnx --sim <id> maestro
335
426
  // …`, silently falling back to the current/saved sim (QA F21-4).
336
427
  const code = await runMaestro(commandArgsWithSim, { port, verbose })
337
428
  // bridge + dynamic imports leave handles open that keep node alive.
@@ -589,4 +680,5 @@ try {
589
680
  throw error
590
681
  }
591
682
 
683
+ await scheduleAutomaticCliUpdate(0)
592
684
  await scheduleAutomaticCleanup()
@@ -1,7 +1,7 @@
1
1
  import { MERGED_CONSOLE_COUNT_EVAL, inspectErrors } from './commands/inspect/core'
2
2
  import { formatLogTimestamp } from './commands/inspect/shared'
3
3
  import type { WsBridge } from './ws-bridge'
4
- import type { SootSimRequestEntry } from '@sootsim/globals'
4
+ import type { SootSimRequestEntry } from '@rnx/globals'
5
5
 
6
6
  interface ConsoleSummaryOptions {
7
7
  includeTail?: boolean
@@ -18,7 +18,7 @@ import type { WsBridge } from './ws-bridge'
18
18
  import type {
19
19
  SootSimExternalAppLifecycleOptions,
20
20
  SootSimLaunchArguments,
21
- } from '@sootsim/globals'
21
+ } from '@rnx/globals'
22
22
 
23
23
  const DEFAULT_TIMEOUT = 10000
24
24
  // hosted-runner previews reach first-render much slower than local dev: the
@@ -174,7 +174,7 @@ export interface MaestroStep {
174
174
  launchApp?: any
175
175
  // maestro canonical form: object with `file` XOR inline `commands`, plus
176
176
  // optional `when` gate and `env` vars scoped to the sub-flow. legacy
177
- // sootsim also accepts a bare path string.
177
+ // rnx also accepts a bare path string.
178
178
  runFlow?:
179
179
  | string
180
180
  | {
@@ -1087,7 +1087,7 @@ export class SootSimBridgeFlowRunner {
1087
1087
  // single tap can land on a transitioning node and never grant focus —
1088
1088
  // the keys then go nowhere and the field stays empty, silently breaking
1089
1089
  // submit (the bluesky login-submit RCA, 2026-06-11). re-resolve + re-tap
1090
- // until the input reports focused, the same gate sootsim's `type-into`
1090
+ // until the input reports focused, the same gate rnx's `type-into`
1091
1091
  // command uses. non-text-input taps keep the single-tap path.
1092
1092
  const tapInputAttempts = element.isTextInput ? 4 : 1
1093
1093
  for (let attempt = 0; attempt < tapInputAttempts; attempt++) {
@@ -1198,14 +1198,14 @@ export class SootSimBridgeFlowRunner {
1198
1198
 
1199
1199
  async inputText(text: string) {
1200
1200
  // real maestro types into the currently-focused field, and its driver's
1201
- // tap synchronously focuses it. in sootsim, focus propagates async across
1201
+ // tap synchronously focuses it. in rnx, focus propagates async across
1202
1202
  // the tenant→shell worker boundary (tap → focusTextInput → keyboard show),
1203
1203
  // so a preceding `tapOn` on a text input may not have landed focus by the
1204
1204
  // time we dispatch keystrokes — especially right after a re-render storm
1205
1205
  // (e.g. bluesky dismissing the custom-server dialog before tapping the
1206
1206
  // username field). without a focused input the keys go nowhere and the
1207
1207
  // field stays empty, silently breaking submit. wait for a focused text
1208
- // input first — the same focus gate sootsim's own `type-into` uses (see
1208
+ // input first — the same focus gate rnx's own `type-into` uses (see
1209
1209
  // the 2026-04-17 bluesky tap-routing regression in commands/inspect.ts).
1210
1210
  if (!(await this.waitForFocusedTextInput())) {
1211
1211
  throw new Error(
@@ -1572,7 +1572,7 @@ export class SootSimBridgeFlowRunner {
1572
1572
  )
1573
1573
  if (typeof tree === 'string') write('tree.txt', tree)
1574
1574
 
1575
- // 6. console buffer + failed requests (captured by sootsim's native-globals
1575
+ // 6. console buffer + failed requests (captured by rnx's native-globals
1576
1576
  // hook / request recorder; same shape as `rnx get errors` etc.).
1577
1577
  const consoleBuf = await safeEval<{
1578
1578
  errors: unknown[]
@@ -1922,7 +1922,7 @@ export class SootSimBridgeFlowRunner {
1922
1922
  })
1923
1923
  await this.waitForTree(120000)
1924
1924
  } else if (wantsResetRuntime) {
1925
- // SootSim benchmark flows need app auth/storage kept warm while the
1925
+ // rnx benchmark flows need app auth/storage kept warm while the
1926
1926
  // guest React runtime starts fresh inside the timed/recorded boundary.
1927
1927
  // the reload re-runs the app entry inside the mounted app surface, so
1928
1928
  // it only does anything while the shell is showing the app. issued from
@@ -2096,7 +2096,7 @@ export class SootSimBridgeFlowRunner {
2096
2096
  await this.rearmCaptureAfterRuntimeReload()
2097
2097
  }
2098
2098
 
2099
- // maestro clearKeychain: there is no real keychain in sootsim's browser
2099
+ // maestro clearKeychain: there is no real keychain in rnx's browser
2100
2100
  // sandbox. log loudly so the author notices, then continue — matching the
2101
2101
  // "loud warning, not silent skip" policy in the plan.
2102
2102
  clearKeychain() {
@@ -2187,7 +2187,7 @@ export class SootSimBridgeFlowRunner {
2187
2187
  return !el
2188
2188
  }
2189
2189
  if (when.platform !== undefined) {
2190
- // sootsim emulates iOS — match against the context's platform the way
2190
+ // rnx emulates iOS — match against the context's platform the way
2191
2191
  // upstream compares against cachedDeviceInfo.platform. keeps
2192
2192
  // cross-platform maestro flows running without stripping guards.
2193
2193
  return when.platform.toLowerCase() === this.js.maestro.platform.toLowerCase()
@@ -1,8 +1,8 @@
1
1
  // browser-side evaluator snippets for CLI inspect commands.
2
2
  //
3
- // the sootsim CLI ships some commands that need to observe the live
4
- // SootSim tree or its rendered pixels. we send these strings over the WS
5
- // bridge where they run inside the sootsim host's `evaluate` handler.
3
+ // the rnx CLI ships some commands that need to observe the live
4
+ // rnx tree or its rendered pixels. we send these strings over the rnx host's
5
+ // `evaluate` handler.
6
6
  //
7
7
  // cropping happens once, in the capture module (`bridges.screenshot`),
8
8
  // via its `crop` option. these evals intentionally defer to that bridge