peachsim 0.1.527

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 (324) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +640 -0
  3. package/cli/app-config.ts +82 -0
  4. package/cli/app-fonts.ts +388 -0
  5. package/cli/app-project.ts +59 -0
  6. package/cli/app-splash.ts +171 -0
  7. package/cli/app-state-reset.ts +26 -0
  8. package/cli/auth.ts +184 -0
  9. package/cli/bin.ts +59 -0
  10. package/cli/bridge-diagnostics.ts +226 -0
  11. package/cli/bridge-flow-runner.ts +2806 -0
  12. package/cli/browser-backend.ts +44 -0
  13. package/cli/browser-evals.ts +96 -0
  14. package/cli/cli-update.ts +261 -0
  15. package/cli/cloud-client.ts +1530 -0
  16. package/cli/cloud-dispatch.ts +206 -0
  17. package/cli/cloud-session.ts +227 -0
  18. package/cli/command-contract.ts +221 -0
  19. package/cli/command-registry.ts +304 -0
  20. package/cli/commands/agent-wrapper.ts +1048 -0
  21. package/cli/commands/agent.ts +439 -0
  22. package/cli/commands/app-fonts.ts +99 -0
  23. package/cli/commands/assert.ts +542 -0
  24. package/cli/commands/auth.ts +71 -0
  25. package/cli/commands/box/bundle-plane.ts +71 -0
  26. package/cli/commands/box/checkout-plane.ts +256 -0
  27. package/cli/commands/box/plane-share.ts +208 -0
  28. package/cli/commands/box/rnx-command.ts +60 -0
  29. package/cli/commands/box/search-command.ts +157 -0
  30. package/cli/commands/box.ts +925 -0
  31. package/cli/commands/camera.ts +266 -0
  32. package/cli/commands/cleanup.ts +169 -0
  33. package/cli/commands/compat.ts +176 -0
  34. package/cli/commands/config.ts +34 -0
  35. package/cli/commands/control.ts +2154 -0
  36. package/cli/commands/cpu-profile.ts +941 -0
  37. package/cli/commands/daemon-mac-app.ts +165 -0
  38. package/cli/commands/daemon.ts +800 -0
  39. package/cli/commands/debug.ts +751 -0
  40. package/cli/commands/desktop.ts +39 -0
  41. package/cli/commands/detect.ts +201 -0
  42. package/cli/commands/detox.ts +481 -0
  43. package/cli/commands/device.ts +134 -0
  44. package/cli/commands/diagnose.ts +589 -0
  45. package/cli/commands/do-chain.ts +408 -0
  46. package/cli/commands/electron.ts +94 -0
  47. package/cli/commands/film.ts +379 -0
  48. package/cli/commands/flow.ts +1240 -0
  49. package/cli/commands/inspect/actions.ts +516 -0
  50. package/cli/commands/inspect/core.ts +2086 -0
  51. package/cli/commands/inspect/count.ts +17 -0
  52. package/cli/commands/inspect/describe.ts +119 -0
  53. package/cli/commands/inspect/env.ts +25 -0
  54. package/cli/commands/inspect/find.ts +169 -0
  55. package/cli/commands/inspect/get-layout.ts +53 -0
  56. package/cli/commands/inspect/keyboard.ts +54 -0
  57. package/cli/commands/inspect/list.ts +175 -0
  58. package/cli/commands/inspect/memory.ts +215 -0
  59. package/cli/commands/inspect/redaction.ts +39 -0
  60. package/cli/commands/inspect/resolve-target.ts +77 -0
  61. package/cli/commands/inspect/screens.ts +78 -0
  62. package/cli/commands/inspect/settle.ts +32 -0
  63. package/cli/commands/inspect/settling.ts +197 -0
  64. package/cli/commands/inspect/shared.ts +259 -0
  65. package/cli/commands/inspect/sleep.ts +15 -0
  66. package/cli/commands/inspect/tree.ts +23 -0
  67. package/cli/commands/inspect/url.ts +17 -0
  68. package/cli/commands/inspect/wait-event.ts +211 -0
  69. package/cli/commands/inspect/wait-idle.ts +25 -0
  70. package/cli/commands/inspect/wait-ready.ts +87 -0
  71. package/cli/commands/inspect/wait-selector.ts +59 -0
  72. package/cli/commands/inspect/wait.ts +32 -0
  73. package/cli/commands/inspect.ts +4738 -0
  74. package/cli/commands/install-cli.ts +275 -0
  75. package/cli/commands/install-desktop.ts +385 -0
  76. package/cli/commands/login.ts +357 -0
  77. package/cli/commands/logout.ts +24 -0
  78. package/cli/commands/maestro-generate.ts +375 -0
  79. package/cli/commands/maestro.ts +536 -0
  80. package/cli/commands/mode.ts +58 -0
  81. package/cli/commands/no-bridge-hint.ts +80 -0
  82. package/cli/commands/perf.ts +67 -0
  83. package/cli/commands/permissions.ts +203 -0
  84. package/cli/commands/platform.ts +410 -0
  85. package/cli/commands/preview.ts +103 -0
  86. package/cli/commands/profile.ts +106 -0
  87. package/cli/commands/react.ts +353 -0
  88. package/cli/commands/record.ts +1435 -0
  89. package/cli/commands/report-issue.ts +305 -0
  90. package/cli/commands/reset.ts +89 -0
  91. package/cli/commands/runtime.ts +348 -0
  92. package/cli/commands/screenshot-command.ts +106 -0
  93. package/cli/commands/screenshot-layers.ts +144 -0
  94. package/cli/commands/screenshot-mode.ts +38 -0
  95. package/cli/commands/screenshot.ts +492 -0
  96. package/cli/commands/screenshots-capture.ts +608 -0
  97. package/cli/commands/screenshots.ts +127 -0
  98. package/cli/commands/serve.ts +139 -0
  99. package/cli/commands/setup.ts +416 -0
  100. package/cli/commands/shell-boolean-mode.ts +82 -0
  101. package/cli/commands/skills.ts +468 -0
  102. package/cli/commands/slides.ts +357 -0
  103. package/cli/commands/state.ts +94 -0
  104. package/cli/commands/storage.ts +58 -0
  105. package/cli/commands/telemetry.ts +55 -0
  106. package/cli/commands/test.ts +535 -0
  107. package/cli/commands/three-mode.ts +771 -0
  108. package/cli/commands/timeline.ts +121 -0
  109. package/cli/commands/upgrade.ts +167 -0
  110. package/cli/commands/upload.ts +1203 -0
  111. package/cli/commands/version.ts +63 -0
  112. package/cli/commands/welcome.ts +17 -0
  113. package/cli/commands/what-happened.ts +328 -0
  114. package/cli/current-sim.ts +217 -0
  115. package/cli/desktop-companion.ts +315 -0
  116. package/cli/drivers/electron.ts +70 -0
  117. package/cli/drivers/index.ts +20 -0
  118. package/cli/drivers/playwright-provisioning.ts +184 -0
  119. package/cli/drivers/playwright-sim-host.ts +537 -0
  120. package/cli/drivers/playwright.ts +209 -0
  121. package/cli/drivers/registry.ts +65 -0
  122. package/cli/drivers/types.ts +102 -0
  123. package/cli/flow-export.ts +55 -0
  124. package/cli/flow-file.ts +142 -0
  125. package/cli/flow-live-status.ts +126 -0
  126. package/cli/flow-session.ts +140 -0
  127. package/cli/help-core.ts +143 -0
  128. package/cli/help.ts +44 -0
  129. package/cli/hidden-runtime-alias.ts +19 -0
  130. package/cli/hints.ts +214 -0
  131. package/cli/inspect-notice-state.ts +114 -0
  132. package/cli/internal-child.ts +176 -0
  133. package/cli/maestro-js.ts +323 -0
  134. package/cli/main.ts +704 -0
  135. package/cli/open-url.ts +8 -0
  136. package/cli/outbound-endpoints.ts +830 -0
  137. package/cli/parent-pid.ts +204 -0
  138. package/cli/parse-args.ts +189 -0
  139. package/cli/privacy.ts +53 -0
  140. package/cli/prompt.ts +56 -0
  141. package/cli/recording-access.ts +108 -0
  142. package/cli/registry.ts +2 -0
  143. package/cli/resolve-assets.ts +63 -0
  144. package/cli/rnx-in-process.ts +98 -0
  145. package/cli/run-registry.ts +226 -0
  146. package/cli/run-rnx-core.ts +148 -0
  147. package/cli/run-rnx.ts +137 -0
  148. package/cli/runtime-notes.ts +66 -0
  149. package/cli/runtime-summary.ts +25 -0
  150. package/cli/secure-file.ts +31 -0
  151. package/cli/self-invocation.ts +58 -0
  152. package/cli/send-to-box.ts +109 -0
  153. package/cli/setup-repository.ts +191 -0
  154. package/cli/shell-init.ts +98 -0
  155. package/cli/standalone.ts +4 -0
  156. package/cli/telemetry.ts +84 -0
  157. package/cli/ui.ts +68 -0
  158. package/cli/ws-bridge.ts +1020 -0
  159. package/detox/attempt-events.cjs +23 -0
  160. package/detox/colors.ts +54 -0
  161. package/detox/config-loader.ts +135 -0
  162. package/detox/element-types.ts +48 -0
  163. package/detox/expectations.ts +534 -0
  164. package/detox/gestures.ts +501 -0
  165. package/detox/index.ts +2694 -0
  166. package/detox/jest-preset.cjs +29 -0
  167. package/detox/jest-setup-after-env.cjs +22 -0
  168. package/detox/matchers.ts +30 -0
  169. package/detox/motion-change.cjs +109 -0
  170. package/detox/motion-change.d.cts +35 -0
  171. package/detox/navigation-lifecycle.ts +290 -0
  172. package/detox/navigation.ts +43 -0
  173. package/detox/proof-frame.cjs +114 -0
  174. package/detox/record-block-execution.cjs +78 -0
  175. package/detox/screenshots/animated-color-test-rest-norngh.png +0 -0
  176. package/detox/screenshots/color-test-after-drag-norngh.png +0 -0
  177. package/detox/screenshots/color-test-rest-norngh.png +0 -0
  178. package/detox/screenshots/theme-blue-toggle.png +0 -0
  179. package/detox/screenshots/theme-blue.png +0 -0
  180. package/detox/screenshots/theme-red-toggle.png +0 -0
  181. package/detox/screenshots/theme-red.png +0 -0
  182. package/dist-lib/agent-daemon-client.cjs +502 -0
  183. package/dist-lib/agent-events.cjs +48 -0
  184. package/dist-lib/agent-identity.cjs +53 -0
  185. package/dist-lib/agent-sessions.cjs +759 -0
  186. package/dist-lib/attached-projects.cjs +526 -0
  187. package/dist-lib/auth/shared-session.cjs +208 -0
  188. package/dist-lib/backend-origin.cjs +88 -0
  189. package/dist-lib/beta.cjs +41 -0
  190. package/dist-lib/beta.mjs +13 -0
  191. package/dist-lib/bridge-constants.cjs +161 -0
  192. package/dist-lib/bridge-contract-input.cjs +451 -0
  193. package/dist-lib/bridge-contract-input.mjs +414 -0
  194. package/dist-lib/bridge-contract.cjs +130 -0
  195. package/dist-lib/bridge-contract.mjs +94 -0
  196. package/dist-lib/capture-contract.cjs +220 -0
  197. package/dist-lib/capture-contract.mjs +190 -0
  198. package/dist-lib/cli-constants.cjs +32 -0
  199. package/dist-lib/cloud-contract.cjs +194 -0
  200. package/dist-lib/cloud-contract.mjs +145 -0
  201. package/dist-lib/cloud.cjs +670 -0
  202. package/dist-lib/cloud.mjs +639 -0
  203. package/dist-lib/config.cjs +96 -0
  204. package/dist-lib/detox/index.cjs +3789 -0
  205. package/dist-lib/detox/jest-preset.cjs +29 -0
  206. package/dist-lib/dev-bundle-resolution.cjs +290 -0
  207. package/dist-lib/home-paths.cjs +638 -0
  208. package/dist-lib/host/bridge-host.cjs +10903 -0
  209. package/dist-lib/host/fetch-proxy-handler.cjs +421 -0
  210. package/dist-lib/host/fetch-proxy-overrides.cjs +63 -0
  211. package/dist-lib/host/fetch-proxy-overrides.mjs +37 -0
  212. package/dist-lib/host/replacement-module-handler.cjs +469 -0
  213. package/dist-lib/host/websocket-proxy.cjs +298 -0
  214. package/dist-lib/index.cjs +4268 -0
  215. package/dist-lib/jump-to-source-babel.cjs +93 -0
  216. package/dist-lib/jump-to-source-native.cjs +112 -0
  217. package/dist-lib/menu.cjs +646 -0
  218. package/dist-lib/menu.mjs +604 -0
  219. package/dist-lib/metro-fingerprint-registry.cjs +186 -0
  220. package/dist-lib/metro-fingerprint-registry.mjs +156 -0
  221. package/dist-lib/metro-production-bundle.cjs +329 -0
  222. package/dist-lib/metro-production-bundle.mjs +295 -0
  223. package/dist-lib/metro.cjs +904 -0
  224. package/dist-lib/profiles.cjs +246 -0
  225. package/dist-lib/public-brand.cjs +39 -0
  226. package/dist-lib/react-native-host-modules.cjs +51 -0
  227. package/dist-lib/react-native-host-modules.mjs +25 -0
  228. package/dist-lib/render-mode.cjs +54 -0
  229. package/dist-lib/scripts/dev-server-scanner.cjs +1232 -0
  230. package/dist-lib/sdk.cjs +2433 -0
  231. package/dist-lib/sdk.mjs +2336 -0
  232. package/dist-lib/skills.cjs +39132 -0
  233. package/dist-lib/swift-submit.cjs +92 -0
  234. package/dist-lib/swift-submit.mjs +65 -0
  235. package/dist-lib/swift.cjs +1646 -0
  236. package/dist-lib/vite.cjs +1591 -0
  237. package/package.json +290 -0
  238. package/scripts/dev-server-scanner.ts +1291 -0
  239. package/scripts/optional-demo-registry.ts +113 -0
  240. package/skills/contrast/SKILL.md +165 -0
  241. package/skills/rnx-debug/SKILL.md +433 -0
  242. package/skills/rnx-perf/SKILL.md +399 -0
  243. package/skills/rnx-setup/SKILL.md +172 -0
  244. package/skills/rnx-test/SKILL.md +321 -0
  245. package/skills/rnx-visual/SKILL.md +126 -0
  246. package/src/agent-daemon-client.ts +410 -0
  247. package/src/agent-events.ts +71 -0
  248. package/src/agent-identity.ts +23 -0
  249. package/src/agent-prompt.ts +71 -0
  250. package/src/agent-sessions.ts +580 -0
  251. package/src/app-url.ts +39 -0
  252. package/src/attached-projects.ts +539 -0
  253. package/src/auth/shared-session.ts +235 -0
  254. package/src/backend-origin.ts +70 -0
  255. package/src/beta.ts +19 -0
  256. package/src/bridge-constants.ts +175 -0
  257. package/src/bridge-contract-input.ts +502 -0
  258. package/src/bridge-contract.ts +558 -0
  259. package/src/browser-cache.ts +16 -0
  260. package/src/capture-contract.ts +587 -0
  261. package/src/cli-constants.ts +1 -0
  262. package/src/cli-version.ts +40 -0
  263. package/src/cloud-contract.ts +274 -0
  264. package/src/cloud.ts +762 -0
  265. package/src/codex-client.ts +234 -0
  266. package/src/config.ts +132 -0
  267. package/src/connect.ts +101 -0
  268. package/src/dev-bundle-resolution.ts +346 -0
  269. package/src/dev-server-open.ts +198 -0
  270. package/src/disk-cleanup.ts +540 -0
  271. package/src/flow-timeout-scale.ts +15 -0
  272. package/src/home-paths.ts +806 -0
  273. package/src/host/agent-host.ts +666 -0
  274. package/src/host/bridge-host.ts +2788 -0
  275. package/src/host/fetch-proxy-handler.ts +449 -0
  276. package/src/host/fetch-proxy-overrides.ts +51 -0
  277. package/src/host/open-url.ts +89 -0
  278. package/src/host/plane-host.ts +203 -0
  279. package/src/host/replacement-module-handler.ts +158 -0
  280. package/src/host/websocket-proxy.ts +315 -0
  281. package/src/index.ts +71 -0
  282. package/src/jump-to-source-babel.ts +135 -0
  283. package/src/jump-to-source-native.ts +102 -0
  284. package/src/menu.ts +777 -0
  285. package/src/metro-fingerprint-registry.ts +302 -0
  286. package/src/metro-plugin.ts +341 -0
  287. package/src/metro-production-bundle.ts +471 -0
  288. package/src/native-dev-bundle-url.ts +118 -0
  289. package/src/native-seam-manifest.ts +341 -0
  290. package/src/playwright-host-log.ts +16 -0
  291. package/src/plist.ts +27 -0
  292. package/src/poll-until.ts +123 -0
  293. package/src/profiles.ts +179 -0
  294. package/src/public-brand.ts +9 -0
  295. package/src/react-native-host-modules.ts +26 -0
  296. package/src/render-mode.ts +27 -0
  297. package/src/rolldown-plugin-swift.ts +240 -0
  298. package/src/runtime-assets.ts +233 -0
  299. package/src/runtime-delivery.ts +54 -0
  300. package/src/screenshots/compose.ts +422 -0
  301. package/src/screenshots/frame-compose.ts +514 -0
  302. package/src/screenshots/orchestrate.ts +256 -0
  303. package/src/screenshots/org-deck.ts +91 -0
  304. package/src/screenshots/registry.ts +58 -0
  305. package/src/screenshots/schema.ts +364 -0
  306. package/src/sdk.ts +15 -0
  307. package/src/settings-bundle.ts +418 -0
  308. package/src/shell-bridge-identity.ts +15 -0
  309. package/src/sim-client.ts +697 -0
  310. package/src/skills/builtin/a11y-review.ts +132 -0
  311. package/src/skills/builtin/compat-check.ts +71 -0
  312. package/src/skills/builtin/maestro-test.ts +121 -0
  313. package/src/skills/builtin/perf-profile.ts +90 -0
  314. package/src/skills/builtin/screenshot-all.ts +52 -0
  315. package/src/skills/builtin/visual-diff.ts +100 -0
  316. package/src/skills/registry.ts +107 -0
  317. package/src/skills/types.ts +41 -0
  318. package/src/swift-submit.ts +185 -0
  319. package/src/swift.ts +7 -0
  320. package/src/team-machine-prompt.ts +73 -0
  321. package/src/vite-plugin-one.ts +264 -0
  322. package/src/vite-plugin-swift.ts +620 -0
  323. package/src/vite-plugin.ts +1946 -0
  324. package/src/worklets-babel.ts +250 -0
package/cli/main.ts ADDED
@@ -0,0 +1,704 @@
1
+ // the rnx CLI proper: argv parsing, the one-time privacy choice, and the
2
+ // single lazy-imported command a run dispatches to. `./bin` imports this only
3
+ // when none of its hidden entry points claimed the process.
4
+
5
+ import { rnxPublicBrand } from '../src/public-brand'
6
+ import { parseArgs, TOP_LEVEL_RUNTIME_COMMANDS } from './parse-args'
7
+ import { RnxExit } from './run-rnx'
8
+ import { rnxSelfInvocation } from './self-invocation'
9
+ import { IS_STANDALONE } from './standalone'
10
+
11
+ // `./help` is loaded lazily (only on help paths) — it statically pulls the full
12
+ // CLI registry renderers and engine settings, which would otherwise add ~400ms
13
+ // of eager chunk load to every command, including hot-path reads like
14
+ // `describe` / `do tap` that never render help. parse-args has no imports, so
15
+ // the startup floor stays at the runtime's own cost (~15ms).
16
+
17
+ // keep top-level dispatch crashes readable. without this, a 10s WS bridge
18
+ // timeout (the most common failure when a sim is dead) prints a 1KB minified
19
+ // chunk of the bundled CLI source straight to stderr, which buries the actual
20
+ // "command timed out after 10s" message and is uncopyable for a bug report.
21
+ // rejections that don't match this shape still get the verbose stack so we
22
+ // don't paper over real bugs.
23
+ function handleTopLevelError(err: unknown): never {
24
+ const msg = err instanceof Error ? err.message : String(err)
25
+ // rnx's known top-level errors are all single-line, narrow strings.
26
+ // everything else gets the full stack so unexpected crashes still surface.
27
+ const isKnownCliFailure =
28
+ /^command timed out after \d+s$/.test(msg) ||
29
+ msg.startsWith('sim disconnected:') ||
30
+ msg.startsWith('bridge never reconnected') ||
31
+ msg.startsWith('multiple sims are connected:') ||
32
+ msg.startsWith('saved sim ') ||
33
+ msg.startsWith('no sim connected with id ') ||
34
+ msg.startsWith('could not connect to ws://') ||
35
+ msg.startsWith('rnx bridge daemon is not running') ||
36
+ msg.startsWith('rnx bridge lockfile is fresh') ||
37
+ msg.startsWith('rnx bridge exited before becoming ready') ||
38
+ msg.startsWith(`${rnxPublicBrand.commandName} open: requested bridge port`) ||
39
+ msg.startsWith('background daemon setup is unavailable') ||
40
+ msg.startsWith('rnx bridge did not start within')
41
+ if (isKnownCliFailure && !process.env.SOOTSIM_VERBOSE) {
42
+ process.stderr.write(` ${msg}\n`)
43
+ process.exit(1)
44
+ }
45
+ if (err instanceof Error && err.stack) {
46
+ process.stderr.write(`${err.stack}\n`)
47
+ } else {
48
+ process.stderr.write(`${msg}\n`)
49
+ }
50
+ process.exit(1)
51
+ }
52
+ process.on('unhandledRejection', handleTopLevelError)
53
+ process.on('uncaughtException', handleTopLevelError)
54
+
55
+ let parsed = parseArgs(process.argv)
56
+ for (const warning of parsed.warnings) process.stderr.write(`${warning}\n`)
57
+
58
+ // runtime actions and reads also work as hidden root aliases. resolve only
59
+ // after the normal parser declined the token, so a real top-level command
60
+ // always wins. keep this lazy so ordinary commands don't load the docs registry.
61
+ if (!parsed.command && parsed.commandArgs.length > 0) {
62
+ const { resolveHiddenRuntimeAlias } = await import('./hidden-runtime-alias')
63
+ parsed = resolveHiddenRuntimeAlias(parsed)
64
+ }
65
+
66
+ const isAutomaticCleanupWorker =
67
+ parsed.command === 'cleanup' &&
68
+ parsed.commandArgs.length === 1 &&
69
+ parsed.commandArgs[0] === '--automatic-worker'
70
+ const isCliUpdateWorker =
71
+ parsed.command === 'upgrade' &&
72
+ parsed.commandArgs.length === 1 &&
73
+ parsed.commandArgs[0] === '--check-worker'
74
+ const isBareWelcome = !parsed.command && parsed.commandArgs.length === 0
75
+ const isReadOnlyInvocation =
76
+ parsed.version || parsed.help || parsed.commandArgs.includes('--dry-run')
77
+
78
+ if (isCliUpdateWorker) {
79
+ if (IS_STANDALONE) {
80
+ try {
81
+ const { refreshCliUpdateCache } = await import('./cli-update')
82
+ await refreshCliUpdateCache()
83
+ } catch {}
84
+ }
85
+ process.exit(0)
86
+ }
87
+
88
+ if (
89
+ !isAutomaticCleanupWorker &&
90
+ !isBareWelcome &&
91
+ !parsed.help &&
92
+ !parsed.version &&
93
+ parsed.command !== 'config' &&
94
+ parsed.command !== 'setup' &&
95
+ parsed.command !== 'telemetry'
96
+ ) {
97
+ const { ensurePrivacyChoice } = await import('./privacy')
98
+ await ensurePrivacyChoice({ prompt: true })
99
+ }
100
+
101
+ // one-shot "engine upgraded in the background" banner. the daemon's hourly
102
+ // auto-updater writes a notice file when it swaps the runtime; whichever
103
+ // interactive command runs next prints it once (stderr, so --json stdout
104
+ // stays parseable) and deletes it. the daemon/serve processes skip it so a
105
+ // background process can't eat the notice before a human sees it.
106
+ if (!isReadOnlyInvocation && parsed.command !== 'serve' && parsed.command !== 'daemon') {
107
+ const { consumeRuntimeUpgradeNotice } = await import('../src/home-paths')
108
+ const notice = consumeRuntimeUpgradeNotice()
109
+ if (notice) {
110
+ process.stderr.write(
111
+ ` ${rnxPublicBrand.name} engine upgraded to v${notice.to}` +
112
+ (notice.from ? ` (from v${notice.from})` : '') +
113
+ ` · what's new: ${rnxPublicBrand.origin}/changelog\n`,
114
+ )
115
+ }
116
+ }
117
+
118
+ let automaticCleanupFinished = false
119
+ async function spawnDetachedSelf(
120
+ args: string[],
121
+ stderr: 'ignore' | 'inherit',
122
+ ): Promise<import('node:child_process').ChildProcess> {
123
+ const { spawn } = await import('node:child_process')
124
+ const options: import('node:child_process').SpawnOptions = {
125
+ detached: true,
126
+ stdio: ['ignore', 'ignore', stderr],
127
+ env: process.env,
128
+ }
129
+ const { executable, prefixArgs } = rnxSelfInvocation()
130
+ return spawn(executable, [...prefixArgs, ...args], options)
131
+ }
132
+
133
+ async function scheduleAutomaticCleanup(): Promise<void> {
134
+ if (automaticCleanupFinished) return
135
+ automaticCleanupFinished = true
136
+ if (
137
+ isReadOnlyInvocation ||
138
+ parsed.command === 'cleanup' ||
139
+ parsed.command === 'serve' ||
140
+ parsed.command === 'agent-wrapper' ||
141
+ (parsed.command === 'daemon' && parsed.commandArgs[0] === 'uninstall')
142
+ ) {
143
+ return
144
+ }
145
+
146
+ try {
147
+ const { shouldSkipAutomaticSootsimCleanup } = await import('../src/disk-cleanup')
148
+ if (shouldSkipAutomaticSootsimCleanup()) return
149
+
150
+ const workerArgs = ['cleanup', '--automatic-worker']
151
+ const child = await spawnDetachedSelf(workerArgs, 'inherit')
152
+ child.once('error', (error) => {
153
+ process.stderr.write(
154
+ ` ${rnxPublicBrand.name} automatic cleanup will retry on the next run: ${error.message}\n`,
155
+ )
156
+ })
157
+ child.unref()
158
+ } catch (error) {
159
+ process.stderr.write(
160
+ ` ${rnxPublicBrand.name} automatic cleanup will retry on the next run: ${
161
+ error instanceof Error ? error.message : String(error)
162
+ }\n`,
163
+ )
164
+ }
165
+ }
166
+
167
+ let automaticCliUpdateFinished = false
168
+ async function scheduleAutomaticCliUpdate(exitCode: number): Promise<void> {
169
+ if (automaticCliUpdateFinished) return
170
+ automaticCliUpdateFinished = true
171
+ if (!IS_STANDALONE) return
172
+ const {
173
+ claimAutomaticCliUpdateCheck,
174
+ shouldUseAutomaticCliUpdateCheck,
175
+ takeCliUpdateNotification,
176
+ } = await import('./cli-update')
177
+ if (
178
+ !shouldUseAutomaticCliUpdateCheck({
179
+ command: parsed.command,
180
+ commandArgs: parsed.commandArgs,
181
+ exitCode,
182
+ standalone: IS_STANDALONE,
183
+ stderrIsTTY: process.stderr.isTTY === true,
184
+ stdoutIsTTY: process.stdout.isTTY === true,
185
+ version: parsed.version,
186
+ })
187
+ ) {
188
+ return
189
+ }
190
+
191
+ const update = takeCliUpdateNotification()
192
+ if (update) {
193
+ process.stderr.write(
194
+ ` update: rnx v${update.currentVersion} → v${update.latestVersion} · run \`rnx upgrade\`\n`,
195
+ )
196
+ }
197
+ if (!claimAutomaticCliUpdateCheck()) return
198
+ try {
199
+ const child = await spawnDetachedSelf(['upgrade', '--check-worker'], 'ignore')
200
+ child.once('error', () => {})
201
+ child.unref()
202
+ } catch {}
203
+ }
204
+
205
+ async function exitWithFlush(code: number): Promise<never> {
206
+ const { flushCliTelemetry } = await import('./telemetry')
207
+ await flushCliTelemetry()
208
+ await scheduleAutomaticCliUpdate(code)
209
+ await scheduleAutomaticCleanup()
210
+ process.exit(code)
211
+ }
212
+
213
+ // --version
214
+ if (parsed.version) {
215
+ // use the single canonical, cached version source — the same one
216
+ // `--help`, `upgrade`, the startup flow, and the bridge-host use. it
217
+ // previously re-read package.json independently here, which could
218
+ // resolve a different copy (stale global install vs repo) and drift
219
+ // from `--help` within one session (QA F20-4).
220
+ const { getCliVersion } = await import('../src/cli-version')
221
+ const { IS_BETA, BETA_LABEL } = await import('../src/beta')
222
+ const suffix = IS_BETA ? ` · ${BETA_LABEL}` : ''
223
+ console.log(`${rnxPublicBrand.commandName} v${getCliVersion()}${suffix}`)
224
+ // runtime version on its own line — keeps the first line parseable while
225
+ // surfacing the (independently-versioned) engine runtime users actually
226
+ // render with. see `rnx upgrade`.
227
+ const { readActiveRuntime } = await import('../src/home-paths')
228
+ const runtime = readActiveRuntime()
229
+ console.log(runtime ? `runtime v${runtime}` : 'runtime not installed')
230
+ await exitWithFlush(0)
231
+ }
232
+
233
+ // --help with no command
234
+ if (parsed.help && !parsed.command) {
235
+ const { printHelp } = await import('./help')
236
+ printHelp()
237
+ await exitWithFlush(0)
238
+ }
239
+
240
+ // apply settings from global flags
241
+ const port = parsed.globalFlags['port'] as number | undefined
242
+ const verbose = parsed.verbose
243
+ const device = parsed.globalFlags['device'] as string | undefined
244
+ const theme = parsed.globalFlags['theme'] as string | undefined
245
+ const homeScreen = parsed.globalFlags['home-screen'] as string | undefined
246
+ const driver = parsed.globalFlags['driver'] as string | undefined
247
+ const headless = (parsed.globalFlags['headless'] as boolean | undefined) === true
248
+ const globalSimTarget = (parsed.globalFlags['sim'] ??
249
+ parsed.globalFlags['session'] ??
250
+ parsed.globalFlags['tab']) as string | undefined
251
+ const commandArgsWithSim = globalSimTarget
252
+ ? ['--sim', globalSimTarget, ...parsed.commandArgs]
253
+ : parsed.commandArgs
254
+
255
+ // apply device/theme/home-screen to settings store if provided
256
+ if (device || theme || homeScreen) {
257
+ const { settingsStore } = await import('sootsim-engine/settings/store')
258
+ const overrides: Record<string, any> = {}
259
+ if (device) overrides.deviceModel = device
260
+ if (theme) overrides.colorScheme = theme
261
+ if (homeScreen) overrides.homeScreenVersion = homeScreen
262
+ settingsStore.apply(overrides)
263
+ }
264
+
265
+ // no command, no args: guide first-time machine setup, then open ConnectRN.
266
+ // returning runs open ConnectRN directly; app discovery belongs inside it.
267
+ if (!parsed.command && parsed.commandArgs.length === 0) {
268
+ const { runWelcome } = await import('./commands/welcome')
269
+ await exitWithFlush(await runWelcome())
270
+ }
271
+
272
+ // route to command. an empty command at this point only happens if the
273
+ // caller passed bare positional args without a verb (e.g. `rnx -- foo`),
274
+ // which used to fall through to the bundler-wrap behavior in `dev`. now that
275
+ // `dev` is gone, treat it as "show help".
276
+ const command = parsed.command ?? ''
277
+ if (!command) {
278
+ // a bare positional that isn't a known command lands here (parse-args
279
+ // leaves `command` null and pushes the token into commandArgs). don't
280
+ // silently dump the full help banner — name the unrecognized token so a
281
+ // typo'd subcommand is obvious. matches the switch `default:` wording.
282
+ if (parsed.commandArgs.length > 0) {
283
+ const attempted =
284
+ parsed.commandArgs.find((a) => !a.startsWith('-')) ?? parsed.commandArgs[0]
285
+ console.error(` unknown command: ${attempted}`)
286
+ console.error(
287
+ ` run \`${rnxPublicBrand.commandName} --help\` to see the full surface.`,
288
+ )
289
+ await exitWithFlush(1)
290
+ }
291
+ const { printHelp } = await import('./help')
292
+ printHelp()
293
+ await exitWithFlush(0)
294
+ }
295
+
296
+ // --help with a command (either global flag or in commandArgs)
297
+ if (
298
+ parsed.help ||
299
+ parsed.commandArgs.includes('--help') ||
300
+ parsed.commandArgs.includes('-h')
301
+ ) {
302
+ if (command === 'skill') {
303
+ const { runSkill } = await import('./commands/skills')
304
+ await runSkill(parsed.commandArgs)
305
+ await exitWithFlush(0)
306
+ }
307
+ if (command === 'state') {
308
+ const { runState } = await import('./commands/state')
309
+ await exitWithFlush(await runState(commandArgsWithSim, { port }))
310
+ }
311
+ if (command === 'reset') {
312
+ const { runReset } = await import('./commands/reset')
313
+ await exitWithFlush(await runReset(commandArgsWithSim, { port }))
314
+ }
315
+ // for grouping verbs, try to resolve per-verb help from the first
316
+ // positional arg. e.g. `rnx do tap --help` shows tap's help page,
317
+ // not the generic `do` grouping page. bare `rnx do --help` falls
318
+ // through to runInspect which has the full verb group listing.
319
+ const isGroupingVerb =
320
+ command === 'do' ||
321
+ command === 'get' ||
322
+ command === 'debug' ||
323
+ command === 'shell' ||
324
+ command === 'perf' ||
325
+ command === 'wait'
326
+ const subVerb = parsed.commandArgs.find((a) => !a.startsWith('-'))
327
+ if (command === 'shell' || command === 'perf') {
328
+ // shell and perf subcommands are documented by their runtime dispatchers.
329
+ // don't try to route through generated per-verb docs.
330
+ } else if (isGroupingVerb && !subVerb) {
331
+ // bare `rnx <group> --help` — render from the registry.
332
+ const { printGroupHelp } = await import('./help')
333
+ if (printGroupHelp(command)) await exitWithFlush(0)
334
+ // fall through — runInspect shows the full help for anything the
335
+ // registry doesn't yet cover (e.g. shell).
336
+ } else {
337
+ const { printCommandHelp } = await import('./help')
338
+ const helpName = isGroupingVerb && subVerb ? subVerb : command
339
+ printCommandHelp(helpName, {
340
+ prefer: isGroupingVerb && subVerb ? 'verb' : 'command',
341
+ group: isGroupingVerb && subVerb ? command : undefined,
342
+ })
343
+ await exitWithFlush(0)
344
+ }
345
+ }
346
+
347
+ const { dispatchCloudBoundary } = await import('./cloud-dispatch')
348
+ const cloudBoundary = await dispatchCloudBoundary({
349
+ command,
350
+ args: commandArgsWithSim,
351
+ })
352
+ if (cloudBoundary.handled) await exitWithFlush(cloudBoundary.code)
353
+ // the command modules stop by throwing RnxExit rather than ending the
354
+ // process, because the same command has to run in a box shell and in
355
+ // workerd, where process.exit() cancels the whole request. this is the
356
+ // host boundary that turns it back into a real exit code.
357
+ try {
358
+ if (TOP_LEVEL_RUNTIME_COMMANDS.has(command)) {
359
+ if (command === 'do') {
360
+ const { hasDoChain, runDoChain } = await import('./commands/do-chain')
361
+ if (hasDoChain(commandArgsWithSim)) {
362
+ await exitWithFlush(await runDoChain(commandArgsWithSim, { port }))
363
+ }
364
+ }
365
+ const { runInspect } = await import('./commands/inspect')
366
+ await runInspect([command, ...commandArgsWithSim], { port, verbose })
367
+ } else {
368
+ switch (command) {
369
+ case 'test': {
370
+ const { runTest } = await import('./commands/test')
371
+ await exitWithFlush(await runTest(commandArgsWithSim, { port, verbose }))
372
+ }
373
+
374
+ case 'assert': {
375
+ // stays outside the usual dispatcher — it spawns self to get the
376
+ // inner verb's --json payload, so it never needs the shared bridge
377
+ // machinery that lives inside runInspect.
378
+ const { runAssert } = await import('./commands/assert')
379
+ await runAssert(parsed.commandArgs)
380
+ break
381
+ }
382
+
383
+ case 'detox': {
384
+ const { runDetox } = await import('./commands/detox')
385
+ await runDetox(parsed.commandArgs, { port, verbose })
386
+ break
387
+ }
388
+
389
+ case 'maestro': {
390
+ const { runMaestro } = await import('./commands/maestro')
391
+ // Maestro forwards playback argv to the shared runner, so it must
392
+ // receive the global `--sim`. passing the bare
393
+ // parsed.commandArgs dropped an explicit `rnx --sim <id> maestro
394
+ // …`, silently falling back to the current/saved sim (QA F21-4).
395
+ const code = await runMaestro(commandArgsWithSim, { port, verbose })
396
+ // bridge + dynamic imports leave handles open that keep node alive.
397
+ await exitWithFlush(typeof code === 'number' ? code : 0)
398
+ }
399
+
400
+ case 'record': {
401
+ const { runRecord } = await import('./commands/record')
402
+ await runRecord(commandArgsWithSim, { port, verbose })
403
+ break
404
+ }
405
+
406
+ case 'film': {
407
+ const { runFilm } = await import('./commands/film')
408
+ // film chains the flow runner, which opens ws bridges + dynamic
409
+ // imports that keep node alive — exit explicitly like `flow` does.
410
+ const code = await runFilm(commandArgsWithSim, { port, verbose })
411
+ await exitWithFlush(typeof code === 'number' ? code : 0)
412
+ }
413
+
414
+ case 'storage': {
415
+ const { runStorage } = await import('./commands/storage')
416
+ const code = await runStorage(commandArgsWithSim, { port, verbose })
417
+ await exitWithFlush(typeof code === 'number' ? code : 0)
418
+ }
419
+
420
+ case 'state': {
421
+ const { runState } = await import('./commands/state')
422
+ const code = await runState(commandArgsWithSim, { port })
423
+ await exitWithFlush(code)
424
+ }
425
+
426
+ case 'reset': {
427
+ const { runReset } = await import('./commands/reset')
428
+ const code = await runReset(commandArgsWithSim, { port })
429
+ await exitWithFlush(code)
430
+ }
431
+
432
+ case 'perf': {
433
+ const { runPerf } = await import('./commands/perf')
434
+ const code = await runPerf(commandArgsWithSim, { port, verbose })
435
+ await exitWithFlush(typeof code === 'number' ? code : 0)
436
+ }
437
+
438
+ case 'screenshot': {
439
+ const { runScreenshotCommand } = await import('./commands/screenshot-command')
440
+ const code = await runScreenshotCommand(commandArgsWithSim, { port, verbose })
441
+ await exitWithFlush(typeof code === 'number' ? code : 0)
442
+ }
443
+
444
+ case 'camera': {
445
+ const { runCamera } = await import('./commands/camera')
446
+ const code = await runCamera(commandArgsWithSim, { port })
447
+ await exitWithFlush(typeof code === 'number' ? code : 0)
448
+ }
449
+
450
+ case 'mode': {
451
+ const { runMode } = await import('./commands/mode')
452
+ await runMode(commandArgsWithSim, { port, verbose })
453
+ break
454
+ }
455
+
456
+ case 'permissions': {
457
+ const { runPermissions } = await import('./commands/permissions')
458
+ const code = await runPermissions(commandArgsWithSim, { port, verbose })
459
+ await exitWithFlush(code)
460
+ }
461
+
462
+ case 'debug': {
463
+ const { runDebug } = await import('./commands/debug')
464
+ await runDebug(commandArgsWithSim, { port, verbose })
465
+ break
466
+ }
467
+
468
+ case 'timeline': {
469
+ const { runTimeline } = await import('./commands/timeline')
470
+ await runTimeline(commandArgsWithSim, { port, verbose })
471
+ break
472
+ }
473
+
474
+ case 'what-happened': {
475
+ const { runWhatHappened } = await import('./commands/what-happened')
476
+ await runWhatHappened(commandArgsWithSim, { port, verbose })
477
+ break
478
+ }
479
+
480
+ case 'open': {
481
+ const { runOpenCommand } = await import('./commands/control')
482
+ await runOpenCommand(commandArgsWithSim, { port })
483
+ break
484
+ }
485
+
486
+ case 'ios':
487
+ case 'android': {
488
+ const { runPlatformCommand } = await import('./commands/platform')
489
+ const code = await runPlatformCommand(command, commandArgsWithSim, { port })
490
+ await exitWithFlush(typeof code === 'number' ? code : 0)
491
+ }
492
+
493
+ case 'remote': {
494
+ if (globalSimTarget) {
495
+ console.error(
496
+ ` ${rnxPublicBrand.commandName} remote creates a new simulator and cannot target --sim`,
497
+ )
498
+ await exitWithFlush(1)
499
+ }
500
+ const platform = parsed.commandArgs[0]
501
+ if (platform === 'close') {
502
+ const { dispatchCloudBoundary } = await import('./cloud-dispatch')
503
+ const result = await dispatchCloudBoundary({
504
+ command: 'close',
505
+ args: parsed.commandArgs.slice(1),
506
+ })
507
+ if (result.handled) {
508
+ await exitWithFlush(result.code)
509
+ }
510
+ await exitWithFlush(0)
511
+ }
512
+ if (platform === 'ios' || platform === 'android') {
513
+ const { runRemotePlatformCommand } = await import('./commands/platform')
514
+ const code = await runRemotePlatformCommand(
515
+ platform,
516
+ parsed.commandArgs.slice(1),
517
+ { port },
518
+ )
519
+ await exitWithFlush(typeof code === 'number' ? code : 0)
520
+ } else if (platform === 'list') {
521
+ const { runRemoteList } = await import('./commands/platform')
522
+ await exitWithFlush(await runRemoteList(parsed.commandArgs.slice(1)))
523
+ } else if (platform === 'stop') {
524
+ const { runRemoteStop } = await import('./commands/platform')
525
+ await exitWithFlush(await runRemoteStop(parsed.commandArgs.slice(1)))
526
+ } else {
527
+ console.error(
528
+ ` usage: ${rnxPublicBrand.commandName} remote ios|android <bundle.js|https://bundle-url>`,
529
+ )
530
+ await exitWithFlush(1)
531
+ }
532
+ }
533
+
534
+ case 'preview': {
535
+ const { runPreview } = await import('./commands/preview')
536
+ await exitWithFlush(await runPreview(parsed.commandArgs))
537
+ }
538
+
539
+ case 'web': {
540
+ const { getRnxCommandAvailability } = await import('./command-registry')
541
+ const availability = getRnxCommandAvailability('web', parsed.commandArgs, 'node')
542
+ console.error(` ${availability.reason ?? 'rnx web is unavailable'}`)
543
+ await exitWithFlush(1)
544
+ }
545
+
546
+ case 'box': {
547
+ const { runBox } = await import('./commands/box')
548
+ await exitWithFlush(await runBox(commandArgsWithSim, { port, verbose }))
549
+ }
550
+
551
+ case 'use': {
552
+ const { runUseCommand } = await import('./commands/control')
553
+ await runUseCommand(commandArgsWithSim, { port })
554
+ break
555
+ }
556
+
557
+ case 'claim': {
558
+ const { runClaimCommand } = await import('./commands/control')
559
+ await runClaimCommand(commandArgsWithSim, { port })
560
+ break
561
+ }
562
+
563
+ case 'close': {
564
+ const { runCloseCommand } = await import('./commands/control')
565
+ await runCloseCommand(commandArgsWithSim, { port })
566
+ break
567
+ }
568
+
569
+ case 'device': {
570
+ const { runDeviceCommand } = await import('./commands/device')
571
+ await runDeviceCommand(commandArgsWithSim, { port })
572
+ break
573
+ }
574
+
575
+ case 'compat': {
576
+ const { runCompat } = await import('./commands/compat')
577
+ await runCompat(parsed.commandArgs)
578
+ break
579
+ }
580
+
581
+ case 'report-issue': {
582
+ const { runReportIssue } = await import('./commands/report-issue')
583
+ const code = await runReportIssue(parsed.commandArgs)
584
+ await exitWithFlush(code)
585
+ }
586
+
587
+ case 'desktop': {
588
+ const { runDesktop } = await import('./commands/desktop')
589
+ await runDesktop(parsed.commandArgs, { port, device })
590
+ break
591
+ }
592
+
593
+ case 'login': {
594
+ const { runLogin } = await import('./commands/login')
595
+ await runLogin(parsed.commandArgs)
596
+ break
597
+ }
598
+
599
+ case 'logout': {
600
+ const { runLogout } = await import('./commands/logout')
601
+ await runLogout()
602
+ break
603
+ }
604
+
605
+ case 'auth': {
606
+ const { runAuth } = await import('./commands/auth')
607
+ await runAuth(parsed.commandArgs)
608
+ break
609
+ }
610
+
611
+ case 'setup': {
612
+ const { runSetup } = await import('./commands/setup')
613
+ await runSetup(parsed.commandArgs)
614
+ break
615
+ }
616
+
617
+ case 'cli': {
618
+ const { runCliInstall } = await import('./commands/install-cli')
619
+ await runCliInstall(parsed.commandArgs)
620
+ break
621
+ }
622
+
623
+ case 'serve': {
624
+ const { runServe } = await import('./commands/serve')
625
+ await runServe(parsed.commandArgs, { port })
626
+ break
627
+ }
628
+
629
+ case 'daemon': {
630
+ const { runDaemon } = await import('./commands/daemon')
631
+ await runDaemon(parsed.commandArgs, { port })
632
+ break
633
+ }
634
+
635
+ case 'runtime': {
636
+ const { runRuntime } = await import('./commands/runtime')
637
+ await runRuntime(parsed.commandArgs)
638
+ break
639
+ }
640
+
641
+ case 'upgrade': {
642
+ const { runUpgrade } = await import('./commands/upgrade')
643
+ await runUpgrade(parsed.commandArgs)
644
+ break
645
+ }
646
+
647
+ case 'version': {
648
+ const { runVersion } = await import('./commands/version')
649
+ await runVersion(parsed.commandArgs)
650
+ break
651
+ }
652
+
653
+ case 'agent': {
654
+ const { runAgentCommand } = await import('./commands/agent')
655
+ const code = await runAgentCommand(parsed.commandArgs, { port })
656
+ await exitWithFlush(code)
657
+ }
658
+
659
+ case 'agent-wrapper': {
660
+ const { runAgentWrapper } = await import('./commands/agent-wrapper')
661
+ const code = await runAgentWrapper(parsed.commandArgs)
662
+ await exitWithFlush(code)
663
+ }
664
+
665
+ case 'skill': {
666
+ const { runSkill } = await import('./commands/skills')
667
+ await runSkill(parsed.commandArgs)
668
+ break
669
+ }
670
+
671
+ case 'app-fonts': {
672
+ const { runAppFonts } = await import('./commands/app-fonts')
673
+ await runAppFonts(parsed.commandArgs)
674
+ break
675
+ }
676
+
677
+ case 'config': {
678
+ const { runConfig } = await import('./commands/config')
679
+ await runConfig(parsed.commandArgs)
680
+ break
681
+ }
682
+
683
+ case 'cleanup': {
684
+ const { runCleanup } = await import('./commands/cleanup')
685
+ const code = await runCleanup(parsed.commandArgs)
686
+ await exitWithFlush(code)
687
+ }
688
+
689
+ default: {
690
+ console.error(` unknown command: ${command}`)
691
+ console.error(
692
+ ` run \`${rnxPublicBrand.commandName} --help\` to see the full surface.`,
693
+ )
694
+ await exitWithFlush(1)
695
+ }
696
+ }
697
+ }
698
+ } catch (error) {
699
+ if (error instanceof RnxExit) await exitWithFlush(error.code)
700
+ throw error
701
+ }
702
+
703
+ await scheduleAutomaticCliUpdate(0)
704
+ await scheduleAutomaticCleanup()