peachsim 0.1.939 → 0.1.1399

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 (396) hide show
  1. package/README.md +28 -22
  2. package/cli/app-catalog.ts +29 -14
  3. package/cli/app-fonts.ts +5 -3
  4. package/cli/bridge-flow-runner.ts +40 -29
  5. package/cli/cloud-client.ts +57 -21
  6. package/cli/cloud-test-tunnel.ts +12 -18
  7. package/cli/commands/agent.ts +1 -1
  8. package/cli/commands/app-fonts.ts +3 -3
  9. package/cli/commands/assert.ts +8 -8
  10. package/cli/commands/auth.ts +8 -3
  11. package/cli/commands/box/bundle-plane.ts +13 -1
  12. package/cli/commands/box.ts +16 -5
  13. package/cli/commands/compat.ts +1 -1
  14. package/cli/commands/config.ts +2 -2
  15. package/cli/commands/control.ts +125 -71
  16. package/cli/commands/cpu-profile.ts +45 -1
  17. package/cli/commands/daemon.ts +131 -33
  18. package/cli/commands/debug.ts +24 -17
  19. package/cli/commands/desktop.ts +7 -1
  20. package/cli/commands/detect.ts +101 -0
  21. package/cli/commands/detox.ts +6 -6
  22. package/cli/commands/device.ts +12 -12
  23. package/cli/commands/electron.ts +32 -14
  24. package/cli/commands/flow.ts +49 -41
  25. package/cli/commands/guest-host.ts +39 -1
  26. package/cli/commands/inspect/core.ts +14 -8
  27. package/cli/commands/inspect/list.ts +1 -1
  28. package/cli/commands/inspect/settle.ts +5 -3
  29. package/cli/commands/inspect/settling.ts +229 -44
  30. package/cli/commands/inspect/wait-idle.ts +3 -2
  31. package/cli/commands/inspect.ts +105 -93
  32. package/cli/commands/install-cli.ts +36 -15
  33. package/cli/commands/install-desktop.ts +68 -195
  34. package/cli/commands/kotlin.ts +6 -5
  35. package/cli/commands/login.ts +5 -5
  36. package/cli/commands/maestro-compare.ts +554 -0
  37. package/cli/commands/maestro-generate.ts +6 -3
  38. package/cli/commands/maestro.ts +22 -0
  39. package/cli/commands/mode.ts +2 -2
  40. package/cli/commands/no-bridge-hint.ts +6 -2
  41. package/cli/commands/platform.ts +35 -6
  42. package/cli/commands/preview.ts +6 -0
  43. package/cli/commands/project-report.ts +10 -12
  44. package/cli/commands/record.ts +37 -37
  45. package/cli/commands/runtime.ts +9 -9
  46. package/cli/commands/screenshot-layers.ts +4 -4
  47. package/cli/commands/screenshot-mode.ts +2 -2
  48. package/cli/commands/screenshot.ts +8 -8
  49. package/cli/commands/screenshots-capture.ts +2 -2
  50. package/cli/commands/serve.ts +9 -12
  51. package/cli/commands/setup.ts +29 -14
  52. package/cli/commands/shell-boolean-mode.ts +2 -2
  53. package/cli/commands/show-touches-mode.ts +2 -2
  54. package/cli/commands/skills.ts +11 -11
  55. package/cli/commands/swift.ts +29 -11
  56. package/cli/commands/telemetry.ts +2 -2
  57. package/cli/commands/test.ts +5 -0
  58. package/cli/commands/three-mode.ts +3 -3
  59. package/cli/commands/timeline.ts +4 -4
  60. package/cli/commands/upgrade.ts +2 -2
  61. package/cli/commands/upload.ts +15 -15
  62. package/cli/commands/what-happened.ts +2 -2
  63. package/cli/current-sim.ts +6 -3
  64. package/cli/desktop-companion.ts +55 -149
  65. package/cli/dev-build-freshness.ts +2 -2
  66. package/cli/drivers/desktop.ts +17 -9
  67. package/cli/drivers/electron.ts +1 -2
  68. package/cli/drivers/playwright-sim-host.ts +11 -2
  69. package/cli/drivers/playwright.ts +35 -2
  70. package/cli/drivers/types.ts +8 -0
  71. package/cli/maestro-compare.ts +670 -0
  72. package/cli/outbound-endpoints.ts +46 -8
  73. package/cli/recording-access.ts +6 -6
  74. package/cli/run-registry.ts +1 -1
  75. package/cli/runtime-compat.ts +5 -1
  76. package/cli/send-to-box.ts +3 -3
  77. package/cli/shell-init.ts +2 -1
  78. package/cli/ws-bridge.ts +9 -5
  79. package/detox/element-types.ts +1 -0
  80. package/detox/gestures.ts +3 -3
  81. package/detox/index.ts +67 -38
  82. package/detox/navigation-lifecycle.ts +9 -6
  83. package/detox/proof-frame.cjs +6 -4
  84. package/dist-lib/agent-daemon-client.cjs +1 -1
  85. package/dist-lib/agent-events.cjs +1 -1
  86. package/dist-lib/attached-projects.cjs +1 -1
  87. package/dist-lib/auth/shared-session.cjs +1 -1
  88. package/dist-lib/backend-origin.cjs +1 -1
  89. package/dist-lib/beta.cjs +1 -1
  90. package/dist-lib/beta.mjs +1 -1
  91. package/dist-lib/bridge-constants.cjs +1 -1
  92. package/dist-lib/bridge-contract-input.cjs +42 -17
  93. package/dist-lib/bridge-contract-input.mjs +41 -16
  94. package/dist-lib/bridge-contract.cjs +1 -4
  95. package/dist-lib/bridge-contract.mjs +1 -3
  96. package/dist-lib/bundle-channel.cjs +2918 -550
  97. package/dist-lib/capture-contract.cjs +65 -24
  98. package/dist-lib/capture-contract.mjs +63 -24
  99. package/dist-lib/cloud-contract.cjs +2 -2
  100. package/dist-lib/cloud-contract.mjs +2 -2
  101. package/dist-lib/cloud-test-args.cjs +1 -1
  102. package/dist-lib/cloud-test-args.mjs +1 -1
  103. package/dist-lib/cloud.cjs +3 -3
  104. package/dist-lib/cloud.mjs +3 -3
  105. package/dist-lib/config.cjs +15 -5
  106. package/dist-lib/config.mjs +154 -0
  107. package/dist-lib/detox/index.cjs +495 -139
  108. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  109. package/dist-lib/expo-swift-bundle.cjs +1168 -238
  110. package/dist-lib/expo-swift-metadata.cjs +34 -23
  111. package/dist-lib/expo-swift-metadata.mjs +32 -23
  112. package/dist-lib/home-paths.cjs +2 -2
  113. package/dist-lib/host/bridge-host.cjs +2938 -654
  114. package/dist-lib/host/fetch-proxy-handler.cjs +14 -1
  115. package/dist-lib/host/fetch-proxy-overrides.cjs +7 -1
  116. package/dist-lib/host/fetch-proxy-overrides.mjs +5 -1
  117. package/dist-lib/host/replacement-module-handler.cjs +2 -2
  118. package/dist-lib/host/websocket-proxy.cjs +1 -1
  119. package/dist-lib/index.cjs +999 -353
  120. package/dist-lib/jump-to-source-babel.cjs +1 -1
  121. package/dist-lib/jump-to-source-native.cjs +1 -1
  122. package/dist-lib/kotlin-host.cjs +332 -19
  123. package/dist-lib/menu.cjs +1 -1
  124. package/dist-lib/menu.mjs +1 -1
  125. package/dist-lib/metro-fingerprint-registry.cjs +1 -1
  126. package/dist-lib/metro-fingerprint-registry.mjs +1 -1
  127. package/dist-lib/metro-production-bundle.cjs +7 -7
  128. package/dist-lib/metro-production-bundle.mjs +5 -5
  129. package/dist-lib/metro.cjs +688 -250
  130. package/dist-lib/native-app-identity-project.cjs +2647 -0
  131. package/dist-lib/native-app-identity-project.mjs +1257 -0
  132. package/dist-lib/native-app-identity.cjs +325 -25
  133. package/dist-lib/native-app-identity.mjs +323 -25
  134. package/dist-lib/native-firebase-metadata.cjs +1 -1
  135. package/dist-lib/native-firebase-metadata.mjs +1 -1
  136. package/dist-lib/plist.cjs +1 -1
  137. package/dist-lib/plist.mjs +1 -1
  138. package/dist-lib/profiles.cjs +1 -1
  139. package/dist-lib/public-brand.cjs +1 -1
  140. package/dist-lib/react-native-host-modules.cjs +5 -1
  141. package/dist-lib/react-native-host-modules.mjs +5 -1
  142. package/dist-lib/scripts/dev-server-scanner.cjs +57 -32
  143. package/dist-lib/sdk.cjs +231 -51
  144. package/dist-lib/sdk.mjs +231 -51
  145. package/dist-lib/skills.cjs +4107 -1374
  146. package/dist-lib/swift-compile.cjs +290 -30
  147. package/dist-lib/swift-compile.mjs +290 -30
  148. package/dist-lib/swift-package.cjs +143 -12
  149. package/dist-lib/swift-package.mjs +137 -11
  150. package/dist-lib/swift-submit.cjs +47 -5
  151. package/dist-lib/swift-submit.mjs +42 -5
  152. package/dist-lib/swift-toolchain.cjs +7 -2
  153. package/dist-lib/swift.cjs +1 -1
  154. package/dist-lib/vite-swift.cjs +1392 -458
  155. package/dist-lib/vite.cjs +13 -17
  156. package/dist-lib/worklet-executable.cjs +248 -33
  157. package/dist-lib/worklet-executable.mjs +248 -33
  158. package/install.sh +10 -1
  159. package/kotlin/{AppSources → AndroidViewSources}/android/content/res/Resources.kt +9 -9
  160. package/kotlin/AndroidViewSources/android/graphics/Canvas.kt +20 -0
  161. package/kotlin/AndroidViewSources/android/view/View.kt +211 -0
  162. package/kotlin/AndroidViewSources/android/widget/FrameLayout.kt +61 -0
  163. package/kotlin/AndroidViewSources/android/widget/LinearLayout.kt +49 -0
  164. package/kotlin/AppSources/android/view/Window.kt +36 -0
  165. package/kotlin/AppSources/android/view/WindowInsets.kt +9 -0
  166. package/kotlin/AppSources/android/view/WindowInsetsController.kt +5 -0
  167. package/kotlin/AppSources/android/window/OnBackInvokedDispatcher.kt +48 -0
  168. package/kotlin/AppSources/androidx/activity/OnBackPressedDispatcher.kt +20 -5
  169. package/kotlin/AppSources/androidx/compose/foundation/layout/WindowInsets.kt +15 -0
  170. package/kotlin/AppSources/androidx/compose/ui/platform/ComposeView.kt +2 -1
  171. package/kotlin/AppSources/dev/peach/app/PeachApp.kt +42 -50
  172. package/kotlin/ComposeSources/PeachComposeHost.kt +200 -84
  173. package/kotlin/ExpoComposeSources/PeachAndroidView.kt +87 -0
  174. package/kotlin/ExpoComposeSources/PeachExpoViewMount.kt +24 -0
  175. package/kotlin/ExpoComposeSources/dev/peach/expo/PeachExpoCompose.kt +28 -0
  176. package/kotlin/ExpoComposeSources/expo/modules/kotlin/views/ExpoComposeView.kt +70 -0
  177. package/kotlin/ExpoSources/dev/peach/expo/PeachExpoRuntime.kt +513 -0
  178. package/kotlin/ExpoSources/dev/peach/expo/PeachExpoTypes.kt +173 -0
  179. package/kotlin/ExpoSources/expo/modules/kotlin/AppContext.kt +20 -0
  180. package/kotlin/ExpoSources/expo/modules/kotlin/Promise.kt +19 -0
  181. package/kotlin/ExpoSources/expo/modules/kotlin/classcomponent/ClassComponentBuilder.kt +198 -0
  182. package/kotlin/ExpoSources/expo/modules/kotlin/exception/CodedException.kt +126 -0
  183. package/kotlin/ExpoSources/expo/modules/kotlin/functions/Functions.kt +132 -0
  184. package/kotlin/ExpoSources/expo/modules/kotlin/jni/JavaScriptTypedArray.kt +81 -0
  185. package/kotlin/ExpoSources/expo/modules/kotlin/modules/Module.kt +98 -0
  186. package/kotlin/ExpoSources/expo/modules/kotlin/objects/ObjectDefinitionBuilder.kt +236 -0
  187. package/kotlin/ExpoSources/expo/modules/kotlin/records/Record.kt +12 -0
  188. package/kotlin/ExpoSources/expo/modules/kotlin/runtime/Runtime.kt +9 -0
  189. package/kotlin/ExpoSources/expo/modules/kotlin/sharedobjects/SharedObject.kt +50 -0
  190. package/kotlin/ExpoSources/expo/modules/kotlin/sharedobjects/SharedObjectRegistry.kt +57 -0
  191. package/kotlin/ExpoSources/expo/modules/kotlin/sharedobjects/SharedRef.kt +19 -0
  192. package/kotlin/ExpoSources/expo/modules/kotlin/typedarray/ConcreteTypedArrays.kt +158 -0
  193. package/kotlin/ExpoSources/expo/modules/kotlin/typedarray/GenericTypedArray.kt +8 -0
  194. package/kotlin/ExpoSources/expo/modules/kotlin/typedarray/TypedArray.kt +29 -0
  195. package/kotlin/ExpoSources/expo/modules/kotlin/typedarray/TypedArrayIterator.kt +9 -0
  196. package/kotlin/ExpoSources/expo/modules/kotlin/types/AnyType.kt +155 -0
  197. package/kotlin/ExpoSources/expo/modules/kotlin/types/Either.kt +91 -0
  198. package/kotlin/ExpoSources/expo/modules/kotlin/types/Enumerable.kt +6 -0
  199. package/kotlin/ExpoSources/expo/modules/kotlin/viewevent/ViewEvent.kt +54 -0
  200. package/kotlin/ExpoSources/expo/modules/kotlin/views/ExpoView.kt +23 -0
  201. package/kotlin/ExpoSources/expo/modules/kotlin/views/ViewDefinitionBuilder.kt +71 -0
  202. package/kotlin/ExpoSources/expo/modules/kotlin/views/ViewProps.kt +35 -0
  203. package/kotlin/README.md +13 -3
  204. package/kotlin/Sources/android/os/Build.kt +17 -0
  205. package/kotlin/Sources/android/os/Handler.kt +35 -0
  206. package/kotlin/Sources/android/os/Looper.kt +17 -0
  207. package/kotlin/Sources/android/os/MessageQueue.kt +56 -0
  208. package/kotlin/Sources/android/os/SystemClock.kt +14 -0
  209. package/kotlin/Sources/android/util/Log.kt +64 -0
  210. package/kotlin/Sources/java/security/MessageDigest.kt +114 -0
  211. package/kotlin/Sources/kotlin/text/StringFormat.kt +9 -0
  212. package/kotlin/Sources/org/json/JSON.kt +44 -0
  213. package/kotlin/fixtures/counter/Counter.d.kt.ts +1 -0
  214. package/kotlin/fixtures/counter/Counter.kt +10 -3
  215. package/kotlin/fixtures/counter/CounterApp.native.tsx +1 -1
  216. package/kotlin/fixtures/counter/vite.config.ts +2 -2
  217. package/kotlin/fixtures/headless-counter/Headless.kt +7 -6
  218. package/kotlin/fixtures/import-animation/Animation.d.kt.ts +7 -0
  219. package/kotlin/fixtures/import-animation/Animation.kt +80 -0
  220. package/kotlin/fixtures/import-animation/ImportAnimation.native.tsx +13 -0
  221. package/kotlin/fixtures/import-animation/README.md +24 -0
  222. package/kotlin/fixtures/import-animation/app/_layout.tsx +4 -0
  223. package/kotlin/fixtures/import-animation/app/index+spa.tsx +1 -0
  224. package/kotlin/fixtures/import-animation/app/routes.d.ts +18 -0
  225. package/kotlin/fixtures/import-animation/package.json +23 -0
  226. package/kotlin/fixtures/import-animation/tsconfig.json +20 -0
  227. package/kotlin/fixtures/import-animation/vite.config.ts +20 -0
  228. package/kotlin/fixtures/import-editor/Editor.d.kt.ts +7 -0
  229. package/kotlin/fixtures/import-editor/Editor.kt +59 -0
  230. package/kotlin/fixtures/import-editor/ImportEditor.native.tsx +23 -0
  231. package/kotlin/fixtures/import-editor/README.md +27 -0
  232. package/kotlin/fixtures/import-editor/app/_layout.tsx +4 -0
  233. package/kotlin/fixtures/import-editor/app/index+spa.tsx +1 -0
  234. package/kotlin/fixtures/import-editor/package.json +23 -0
  235. package/kotlin/fixtures/import-editor/tsconfig.json +20 -0
  236. package/kotlin/fixtures/import-editor/vite.config.ts +20 -0
  237. package/kotlin/fixtures/import-effects/Effects.d.kt.ts +7 -0
  238. package/kotlin/fixtures/import-effects/Effects.kt +87 -0
  239. package/kotlin/fixtures/import-effects/ImportEffects.native.tsx +13 -0
  240. package/kotlin/fixtures/import-effects/README.md +19 -0
  241. package/kotlin/fixtures/import-effects/app/_layout.tsx +4 -0
  242. package/kotlin/fixtures/import-effects/app/index+spa.tsx +1 -0
  243. package/kotlin/fixtures/import-effects/package.json +23 -0
  244. package/kotlin/fixtures/import-effects/tsconfig.json +20 -0
  245. package/kotlin/fixtures/import-effects/vite.config.ts +20 -0
  246. package/kotlin/fixtures/import-layout/ImportLayout.native.tsx +13 -0
  247. package/kotlin/fixtures/import-layout/Layout.d.kt.ts +7 -0
  248. package/kotlin/fixtures/import-layout/Layout.kt +72 -0
  249. package/kotlin/fixtures/import-layout/README.md +24 -0
  250. package/kotlin/fixtures/import-layout/app/_layout.tsx +4 -0
  251. package/kotlin/fixtures/import-layout/app/index+spa.tsx +1 -0
  252. package/kotlin/fixtures/import-layout/package.json +23 -0
  253. package/kotlin/fixtures/import-layout/tsconfig.json +20 -0
  254. package/kotlin/fixtures/import-layout/vite.config.ts +20 -0
  255. package/kotlin/fixtures/import-navigation/ImportNavigation.native.tsx +18 -0
  256. package/kotlin/fixtures/import-navigation/Navigation.d.kt.ts +7 -0
  257. package/kotlin/fixtures/import-navigation/Navigation.kt +104 -0
  258. package/kotlin/fixtures/import-navigation/README.md +31 -0
  259. package/kotlin/fixtures/import-navigation/app/_layout.tsx +4 -0
  260. package/kotlin/fixtures/import-navigation/app/index+spa.tsx +1 -0
  261. package/kotlin/fixtures/import-navigation/package.json +23 -0
  262. package/kotlin/fixtures/import-navigation/tsconfig.json +20 -0
  263. package/kotlin/fixtures/import-navigation/vite.config.ts +20 -0
  264. package/kotlin/fixtures/import-overlays/ImportOverlays.native.tsx +29 -0
  265. package/kotlin/fixtures/import-overlays/Overlays.d.kt.ts +7 -0
  266. package/kotlin/fixtures/import-overlays/Overlays.kt +93 -0
  267. package/kotlin/fixtures/import-overlays/README.md +34 -0
  268. package/kotlin/fixtures/import-overlays/app/_layout.tsx +4 -0
  269. package/kotlin/fixtures/import-overlays/app/index+spa.tsx +1 -0
  270. package/kotlin/fixtures/import-overlays/package.json +23 -0
  271. package/kotlin/fixtures/import-overlays/tsconfig.json +20 -0
  272. package/kotlin/fixtures/import-overlays/vite.config.ts +20 -0
  273. package/kotlin/fixtures/import-scroll/ImportScroll.native.tsx +13 -0
  274. package/kotlin/fixtures/import-scroll/README.md +23 -0
  275. package/kotlin/fixtures/import-scroll/Scroll.d.kt.ts +7 -0
  276. package/kotlin/fixtures/import-scroll/Scroll.kt +91 -0
  277. package/kotlin/fixtures/import-scroll/app/_layout.tsx +4 -0
  278. package/kotlin/fixtures/import-scroll/app/index+spa.tsx +1 -0
  279. package/kotlin/fixtures/import-scroll/package.json +23 -0
  280. package/kotlin/fixtures/import-scroll/tsconfig.json +20 -0
  281. package/kotlin/fixtures/import-scroll/vite.config.ts +20 -0
  282. package/kotlin/fixtures/import-selection/ImportSelection.native.tsx +44 -0
  283. package/kotlin/fixtures/import-selection/README.md +32 -0
  284. package/kotlin/fixtures/import-selection/Selection.d.kt.ts +8 -0
  285. package/kotlin/fixtures/import-selection/Selection.kt +78 -0
  286. package/kotlin/fixtures/import-selection/app/_layout.tsx +4 -0
  287. package/kotlin/fixtures/import-selection/app/index+spa.tsx +1 -0
  288. package/kotlin/fixtures/import-selection/package.json +23 -0
  289. package/kotlin/fixtures/import-selection/tsconfig.json +20 -0
  290. package/kotlin/fixtures/import-selection/vite.config.ts +20 -0
  291. package/kotlin/fixtures/import-surfaces/ImportSurfaces.native.tsx +13 -0
  292. package/kotlin/fixtures/import-surfaces/README.md +22 -0
  293. package/kotlin/fixtures/import-surfaces/Surfaces.d.kt.ts +7 -0
  294. package/kotlin/fixtures/import-surfaces/Surfaces.kt +64 -0
  295. package/kotlin/fixtures/import-surfaces/app/_layout.tsx +4 -0
  296. package/kotlin/fixtures/import-surfaces/app/index+spa.tsx +1 -0
  297. package/kotlin/fixtures/import-surfaces/package.json +23 -0
  298. package/kotlin/fixtures/import-surfaces/tsconfig.json +20 -0
  299. package/kotlin/fixtures/import-surfaces/vite.config.ts +20 -0
  300. package/kotlin/fixtures/import-values/ImportValues.native.tsx +35 -0
  301. package/kotlin/fixtures/import-values/README.md +23 -0
  302. package/kotlin/fixtures/import-values/Values.d.kt.ts +11 -0
  303. package/kotlin/fixtures/import-values/Values.kt +66 -0
  304. package/kotlin/fixtures/import-values/app/_layout.tsx +4 -0
  305. package/kotlin/fixtures/import-values/app/index+spa.tsx +1 -0
  306. package/kotlin/fixtures/import-values/package.json +23 -0
  307. package/kotlin/fixtures/import-values/tsconfig.json +20 -0
  308. package/kotlin/fixtures/import-values/vite.config.ts +20 -0
  309. package/kotlin/fixtures/kotlin-app-variants/BarBehavior.kt +38 -0
  310. package/kotlin/fixtures/kotlin-app-variants/BarVisibility.kt +36 -0
  311. package/kotlin/fixtures/kotlin-app-variants/Ime.kt +3 -1
  312. package/kotlin/fixtures/kotlin-app-variants/TransientInsets.kt +50 -0
  313. package/kotlin/fork/NOTICE +6 -1
  314. package/kotlin/fork/README.md +15 -7
  315. package/kotlin/fork/build.sh +2 -0
  316. package/kotlin/fork/compose-worker-globals.patch +186 -4
  317. package/kotlin/fork/fork.json +2 -2
  318. package/kotlin/fork/skiko-worker-clock.patch +12 -0
  319. package/kotlin/toolchain.json +12 -12
  320. package/kotlin/tools/expo-types.main.kts +115 -0
  321. package/package.json +17 -23
  322. package/scripts/dev-server-scanner.ts +92 -66
  323. package/skills/contrast/SKILL.md +2 -2
  324. package/skills/peach-debug/SKILL.md +1 -1
  325. package/skills/peach-perf/SKILL.md +1 -1
  326. package/skills/peach-setup/SKILL.md +2 -1
  327. package/skills/peach-test/SKILL.md +8 -0
  328. package/src/assetCatalogContract.ts +43 -1
  329. package/src/bridge-contract-input.ts +46 -19
  330. package/src/bridge-contract.ts +0 -1
  331. package/src/bundle-channel.mjs +197 -84
  332. package/src/bundleGlobalFooter.ts +38 -0
  333. package/src/capture-contract.ts +107 -33
  334. package/src/cloud-contract.ts +5 -1
  335. package/src/cloud.ts +5 -2
  336. package/src/config.ts +22 -3
  337. package/src/configured-bundle-producer.ts +1 -1
  338. package/src/desktop-host-delivery.ts +95 -1
  339. package/src/expo-kotlin-bundle.ts +29 -0
  340. package/src/expo-kotlin-host.ts +201 -0
  341. package/src/expo-swift-host.ts +96 -76
  342. package/src/expoKotlinContract.ts +70 -0
  343. package/src/expoSwiftContract.ts +4 -25
  344. package/src/home-paths.ts +7 -4
  345. package/src/host/bridge-host.ts +23 -3
  346. package/src/host/fetch-proxy-handler.ts +13 -0
  347. package/src/host/fetch-proxy-overrides.ts +11 -0
  348. package/src/host/replacement-module-handler.ts +5 -2
  349. package/src/index.ts +3 -1
  350. package/src/kotlin-host.ts +463 -19
  351. package/src/metro-plugin.ts +56 -28
  352. package/src/metro-production-bundle.ts +2 -2
  353. package/src/native-app-identity-project.ts +190 -0
  354. package/src/native-app-identity.ts +398 -34
  355. package/src/native-firebase-config.ts +69 -15
  356. package/src/native-seam-manifest.ts +6 -0
  357. package/src/one-widget-discovery.ts +2 -1
  358. package/src/openstep-plist.ts +107 -0
  359. package/src/react-native-host-modules.ts +4 -0
  360. package/src/runtime-assets.ts +2 -2
  361. package/src/sim-client.ts +2 -2
  362. package/src/swift-binary-modules.ts +3 -1
  363. package/src/swift-compile.ts +286 -38
  364. package/src/swift-distribution-contract.ts +4 -0
  365. package/src/swift-distribution.lock.json +123 -12
  366. package/src/swift-distribution.ts +7 -3
  367. package/src/swift-host.ts +298 -83
  368. package/src/swift-package-manifest.ts +29 -2
  369. package/src/swift-package.ts +180 -10
  370. package/src/swift-packages.ts +4 -0
  371. package/src/swift-standin-emit.ts +4 -1
  372. package/src/swift-standins.ts +274 -15
  373. package/src/swift-submit.ts +62 -7
  374. package/src/swift-toolchain.ts +6 -1
  375. package/src/swiftContract.ts +126 -3
  376. package/src/vite-plugin-one.ts +13 -16
  377. package/src/vite-plugin-swift.ts +4 -5
  378. package/src/vite-plugin.ts +360 -144
  379. package/src/vite.d.ts +1 -1
  380. package/src/worker-system-chunks.ts +221 -0
  381. package/src/worklet-executable.ts +348 -38
  382. package/src/worklets-babel.ts +1 -1
  383. package/src/xcode-project.ts +243 -127
  384. package/dist-lib/agent-identity.cjs +0 -39
  385. package/dist-lib/agent-sessions.cjs +0 -760
  386. package/dist-lib/cli-constants.cjs +0 -32
  387. package/dist-lib/render-mode.cjs +0 -44
  388. package/kotlin/AppSources/android/view/View.kt +0 -74
  389. package/kotlin/AppSources/android/widget/FrameLayout.kt +0 -6
  390. package/src/render-mode.ts +0 -15
  391. /package/kotlin/{AppSources → AndroidViewSources}/android/content/Context.kt +0 -0
  392. /package/kotlin/{AppSources → AndroidViewSources}/android/content/res/Configuration.kt +0 -0
  393. /package/kotlin/{AppSources → AndroidViewSources}/android/graphics/Color.kt +0 -0
  394. /package/kotlin/{AppSources → AndroidViewSources}/android/util/DisplayMetrics.kt +0 -0
  395. /package/kotlin/{AppSources → AndroidViewSources}/dev/peach/app/JavaFormat.kt +0 -0
  396. /package/kotlin/{AppSources → Sources}/java/lang/Runnable.kt +0 -0
@@ -30,6 +30,7 @@ function isDeviceModel(value: string): value is DeviceModel {
30
30
 
31
31
  // flags this command reads for itself rather than treating as the bundle.
32
32
  const REMOTE_VALUE_FLAGS = new Set([
33
+ '--assets',
33
34
  '--device',
34
35
  '--name',
35
36
  '--endpoint',
@@ -41,12 +42,14 @@ function remoteInputFromArgs(
41
42
  platform: RuntimePlatform,
42
43
  ): {
43
44
  bundlePath: string
45
+ assetsDirectory: string | undefined
44
46
  name: string | null
45
47
  nativeProjectDir: string | undefined
46
48
  reuse: boolean
47
49
  fresh: boolean
48
50
  } {
49
51
  const positional: string[] = []
52
+ let assetsDirectory: string | undefined
50
53
  let name: string | null = null
51
54
  let nativeProjectDir: string | undefined
52
55
  for (let index = 0; index < args.length; index++) {
@@ -57,6 +60,7 @@ function remoteInputFromArgs(
57
60
  if (REMOTE_VALUE_FLAGS.has(arg)) {
58
61
  const value = args[++index] ?? null
59
62
  if (!value) throw new Error(`peach remote ${platform} ${arg} requires a value`)
63
+ if (arg === '--assets') assetsDirectory = value
60
64
  if (arg === '--name') name = value
61
65
  if (arg === '--native-project') nativeProjectDir = value
62
66
  continue
@@ -66,6 +70,7 @@ function remoteInputFromArgs(
66
70
  const value = assigned[2] ?? ''
67
71
  if (!value)
68
72
  throw new Error(`peach remote ${platform} ${assigned[1]} requires a value`)
73
+ if (assigned[1] === '--assets') assetsDirectory = value
69
74
  if (assigned[1] === '--name') name = value
70
75
  if (assigned[1] === '--native-project') nativeProjectDir = value
71
76
  continue
@@ -97,7 +102,14 @@ function remoteInputFromArgs(
97
102
  if (reuse && fresh) {
98
103
  throw new Error(`peach remote ${platform} cannot combine --reuse and --new`)
99
104
  }
100
- return { bundlePath: positional[0], name, nativeProjectDir, reuse, fresh }
105
+ return {
106
+ bundlePath: positional[0],
107
+ assetsDirectory,
108
+ name,
109
+ nativeProjectDir,
110
+ reuse,
111
+ fresh,
112
+ }
101
113
  }
102
114
 
103
115
  /**
@@ -163,7 +175,7 @@ export async function runPlatformCommand(
163
175
  }
164
176
  }
165
177
 
166
- if (args.includes('--remote')) {
178
+ if (args.includes('--remote') || args.includes('--nano')) {
167
179
  if (platform === 'android' && args.includes('--nano')) {
168
180
  const error = new UnsupportedRemoteHostError('android', 'nano')
169
181
  console.error(` ${error.message}`)
@@ -176,8 +188,9 @@ export async function runPlatformCommand(
176
188
  return await runRemoteCreate(platform, args, selectedDevice)
177
189
  } catch (error) {
178
190
  rethrowIfExit(error)
191
+ const mode = args.includes('--nano') ? '--nano' : '--remote'
179
192
  console.error(
180
- ` ${rnxPublicBrand.commandName} ${platform} --remote failed: ${
193
+ ` ${rnxPublicBrand.commandName} ${platform} ${mode} failed: ${
181
194
  error instanceof Error ? error.message : String(error)
182
195
  }`,
183
196
  )
@@ -276,9 +289,15 @@ async function runRemoteCreate(
276
289
  import('../cloud-session'),
277
290
  import('./box'),
278
291
  ])
279
- const artifact = await produceCloudArtifact(input.bundlePath)
292
+ const artifact = await produceCloudArtifact(
293
+ input.bundlePath,
294
+ undefined,
295
+ input.assetsDirectory,
296
+ )
280
297
  const descriptor = requireCloudSessionShellUpdate()
281
- const { auth, header } = authHeaderOrExit(`${platform} --remote`)
298
+ const { auth, header } = authHeaderOrExit(
299
+ `${platform} ${args.includes('--nano') ? '--nano' : '--remote'}`,
300
+ )
282
301
  const accountId = cloudAccountIdOrExit(auth)
283
302
 
284
303
  const existingSession = readCloudSession()
@@ -325,6 +344,7 @@ async function runRemoteCreate(
325
344
  accountId,
326
345
  platform,
327
346
  reuse: input.reuse,
347
+ assetsDirectory: input.assetsDirectory,
328
348
  })
329
349
  try {
330
350
  publishCloudSessionToShell(created.session, descriptor)
@@ -405,7 +425,7 @@ async function runRemoteBox(
405
425
  import('../cloud-session'),
406
426
  ])
407
427
  const { openInBrowser } = box
408
- const artifact = await produceCloudBoxArtifact(input.bundlePath)
428
+ const artifact = await produceCloudBoxArtifact(input.bundlePath, input.assetsDirectory)
409
429
  // the session records which bundle a box already holds, so reattaching can
410
430
  // tell the same app from a rebuilt one. the box producer returns no digest
411
431
  // of its own, so take it here over the bytes that actually crossed.
@@ -498,10 +518,12 @@ async function runRemoteBox(
498
518
  await closeCloudBox(session)
499
519
  throw error
500
520
  }
521
+ let attachedToShell = true
501
522
  try {
502
523
  publishCloudSessionToShell(session)
503
524
  } catch {
504
525
  // shell update fd not configured
526
+ attachedToShell = false
505
527
  }
506
528
  process.env[PEACH_CLOUD_SESSION_ENV] = serializeCloudSession(session)
507
529
 
@@ -514,6 +536,13 @@ async function runRemoteBox(
514
536
  if (artifact.assetsDirectory) console.log(` asset dest: ${artifact.assetsDirectory}`)
515
537
  for (const warning of artifact.warnings) console.warn(` warning: ${warning}`)
516
538
  console.log(REMOTE_COMMANDS_HELP)
539
+ // without the shell integration no later command reaches this box, so
540
+ // `peach remote close` cannot end it; name the command that does.
541
+ if (!attachedToShell) {
542
+ console.log(
543
+ ` this shell has no Peach integration; the Box bills until: ${rnxPublicBrand.commandName} box stop ${created.id}`,
544
+ )
545
+ }
517
546
  // the session is published before the grant is minted, so a grant that never
518
547
  // arrives still leaves the box addressable: re-running this command mints it.
519
548
  const watchUrl = await requestBoxWatchUrl({
@@ -118,12 +118,18 @@ export async function runPreview(args: string[]): Promise<number> {
118
118
  JSON.stringify({
119
119
  previewId: finalize.id,
120
120
  previewUrl: url,
121
+ visibility: finalize.visibility,
121
122
  platform,
122
123
  artifact: artifact.receipt,
123
124
  }),
124
125
  )
125
126
  else {
126
127
  console.log(` preview: ${url}`)
128
+ if (finalize.visibility === 'private') {
129
+ console.log(
130
+ " private: only signed-in members of your account can open it; turn on Public link in the page's share menu to share it",
131
+ )
132
+ }
127
133
  console.log(
128
134
  ` artifact: sha256:${artifact.sha256} (${artifact.bytes.byteLength} bytes)`,
129
135
  )
@@ -15,6 +15,7 @@ import {
15
15
  readProjectJson,
16
16
  readProjectManifest,
17
17
  readProjectMetadata,
18
+ readNativeProjectRenderer,
18
19
  resolveManifestVersions,
19
20
  scanRepository,
20
21
  } from './detect'
@@ -113,7 +114,7 @@ export function createProjectReport(
113
114
  const lockCache = new Map<string, Map<string, string>>()
114
115
  const notes = [
115
116
  'Only project paths, dependency versions and toolchain settings are included. No source, environment values, URLs or credentials.',
116
- 'Config is inspected without execution. Unspecified or dynamic engine/architecture settings are reported as unknown.',
117
+ 'Config is inspected without execution. Unspecified settings are reported as unknown; malformed, dynamic or contradictory explicit native architecture assignments are rejected.',
117
118
  'Directories named node_modules, Pods, Carthage, vendor, build, dist, dist-lib, DerivedData, coverage, target, tmp, cache, artifacts, test-results, playwright-report, and hidden directories are excluded. Directory symlinks are not followed.',
118
119
  'Limits: 12 directory levels, 10,000 directories, 200,000 entries, 4 MiB per metadata file. Paths are relative to the scanned root.',
119
120
  ]
@@ -151,26 +152,23 @@ export function createProjectReport(
151
152
  const gradle = readProjectMetadata(join(dir, 'android/gradle.properties')) ?? ''
152
153
  const engine =
153
154
  metadataValue(expo, 'jsEngine') ?? metadataValue(podProperties, 'expo.jsEngine')
154
- const architecture =
155
- metadataValue(expo, 'newArchEnabled') ??
156
- metadataValue(podProperties, 'newArchEnabled')
155
+ const iosRenderer = readNativeProjectRenderer(dir, 'ios')
156
+ const androidRenderer = readNativeProjectRenderer(dir, 'android')
157
157
  const settings: Record<string, string> = {
158
158
  jsEngine:
159
159
  engine === 'hermes' || engine === 'jsc'
160
160
  ? `${engine} (configured)`
161
161
  : 'unknown (not configured in static metadata)',
162
- newArchitecture:
163
- typeof architecture === 'boolean' ||
164
- architecture === 'true' ||
165
- architecture === 'false'
166
- ? `${architecture} (configured)`
167
- : 'unknown (not configured in static metadata)',
162
+ newArchitecture: iosRenderer
163
+ ? `${iosRenderer === 'fabric'} (configured; iOS)`
164
+ : 'unknown (not configured in static metadata)',
165
+ androidNewArchitecture: androidRenderer
166
+ ? `${androidRenderer === 'fabric'} (configured; Android)`
167
+ : 'unknown (not configured in static metadata)',
168
168
  }
169
169
  const hermes = /^hermesEnabled\s*=\s*(true|false)\s*$/m.exec(gradle)
170
- const newArch = /^newArchEnabled\s*=\s*(true|false)\s*$/m.exec(gradle)
171
170
  if (hermes)
172
171
  settings.androidJsEngine = `${hermes[1] === 'true' ? 'hermes' : 'jsc'} (gradle.properties)`
173
- if (newArch) settings.androidNewArchitecture = `${newArch[1]} (gradle.properties)`
174
172
  if (resolved.expo)
175
173
  settings.expoSdk = `${sanitizeProjectReportVersion(resolved.expo.version)} (${resolved.expo.source}; expo package version)`
176
174
  projects.push({
@@ -18,7 +18,7 @@ import { getCliIdentityKey } from '../current-sim'
18
18
  import { openUrl } from '../open-url'
19
19
  import { ensureCliRecordingEntitlement } from '../recording-access'
20
20
  import { resolveRunProvenance } from '../run-registry'
21
- import { rnxExit } from '../run-rnx'
21
+ import { peachExit } from '../run-rnx'
22
22
  import {
23
23
  checkSimHealth,
24
24
  createBridgeFromParsed,
@@ -88,7 +88,7 @@ async function evalCaptureStep<T>(
88
88
  ` the capture lock auto-releases, so simply retry the recording.\n` +
89
89
  ` if it persists, recover the sim with \`peach close --sim <id>\`.`,
90
90
  )
91
- rnxExit(1)
91
+ peachExit(1)
92
92
  }
93
93
  // a leaked/contended render-host streaming session surfaces as an
94
94
  // engine-side error delivered over the WS bridge. on the gif/video
@@ -109,12 +109,12 @@ async function evalCaptureStep<T>(
109
109
  )}s — retry then, or recover now with\n` +
110
110
  ` \`peach close --sim <id>\` and reopen the sim.`,
111
111
  )
112
- rnxExit(1)
112
+ peachExit(1)
113
113
  }
114
114
  // never let any other capture-eval rejection escape as an unhandled
115
115
  // promise (raw stack + false exit 0). surface it and exit non-zero.
116
116
  console.error(` ${step} failed: ${msg}`)
117
- rnxExit(1)
117
+ peachExit(1)
118
118
  }
119
119
  }
120
120
 
@@ -174,7 +174,7 @@ function readRepoScope(args: string[]): RepoScope {
174
174
  const repo = valueOf(args, '--repo')?.trim()
175
175
  if ((owner && !repo) || (!owner && repo)) {
176
176
  console.error(' --owner and --repo must be provided together')
177
- rnxExit(1)
177
+ peachExit(1)
178
178
  }
179
179
  return owner && repo ? { owner, repo } : null
180
180
  }
@@ -238,7 +238,7 @@ export async function runRecord(args: string[], opts: RecordOptions) {
238
238
  const formatArg = valueOf(args, '--format') as OutputFormat | undefined
239
239
  if (formatArg && !['webm', 'mp4', 'gif', 'png'].includes(formatArg)) {
240
240
  console.error(` invalid --format "${formatArg}" — expected webm | mp4 | gif | png`)
241
- rnxExit(1)
241
+ peachExit(1)
242
242
  }
243
243
  const outputArg = valueOf(args, '--output')
244
244
  const durationSec = Number(valueOf(args, '--duration') ?? '10')
@@ -246,7 +246,7 @@ export async function runRecord(args: string[], opts: RecordOptions) {
246
246
  console.error(
247
247
  ` invalid --duration "${valueOf(args, '--duration')}" — expected a positive number of seconds`,
248
248
  )
249
- rnxExit(1)
249
+ peachExit(1)
250
250
  }
251
251
  // upper bound: a fat-fingered `--duration 99999` (≈27.7h) otherwise
252
252
  // started a runaway recording with no warning (QA F23-3). 600s mirrors
@@ -260,7 +260,7 @@ export async function runRecord(args: string[], opts: RecordOptions) {
260
260
  console.error(
261
261
  ` invalid --duration "${valueOf(args, '--duration')}" — exceeds the ${MAX_DURATION_SEC}s (10m) maximum`,
262
262
  )
263
- rnxExit(1)
263
+ peachExit(1)
264
264
  }
265
265
  const fps = Number(valueOf(args, '--fps') ?? '30')
266
266
  const framesArg = valueOf(args, '--frames')
@@ -273,7 +273,7 @@ export async function runRecord(args: string[], opts: RecordOptions) {
273
273
  console.error(
274
274
  ` invalid --frames "${framesArg}" — expected a positive integer count`,
275
275
  )
276
- rnxExit(1)
276
+ peachExit(1)
277
277
  }
278
278
  }
279
279
  const openAfter = args.includes('--open')
@@ -287,7 +287,7 @@ export async function runRecord(args: string[], opts: RecordOptions) {
287
287
  console.error(
288
288
  ' --lockstep works with `record start` / `record stop` (stateful mode) — atomic --duration is wall-clock and cannot bound a virtual-time capture',
289
289
  )
290
- rnxExit(1)
290
+ peachExit(1)
291
291
  }
292
292
 
293
293
  const durationMs = Math.max(100, Math.round(durationSec * 1000))
@@ -344,7 +344,7 @@ export async function runRecord(args: string[], opts: RecordOptions) {
344
344
  ` start failed: recording store refused to start (${mode})` +
345
345
  (started.error ? `: ${started.error}` : ''),
346
346
  )
347
- rnxExit(1)
347
+ peachExit(1)
348
348
  }
349
349
  console.log(` recording ${mode} for ${durationSec}s`)
350
350
  await new Promise((r) => setTimeout(r, durationMs))
@@ -403,7 +403,7 @@ export async function runRecord(args: string[], opts: RecordOptions) {
403
403
  })
404
404
  if (!start.ok || !start.requestId) {
405
405
  console.error(` frame capture start failed: ${start.error ?? 'unknown error'}`)
406
- rnxExit(1)
406
+ peachExit(1)
407
407
  }
408
408
  await new Promise((r) => setTimeout(r, durationMs))
409
409
  const deadline = Date.now() + Math.max(5_000, durationMs)
@@ -420,19 +420,19 @@ export async function runRecord(args: string[], opts: RecordOptions) {
420
420
  })
421
421
  if (!result) {
422
422
  console.error(' frame capture result missing')
423
- rnxExit(1)
423
+ peachExit(1)
424
424
  }
425
425
  if (result.done) {
426
426
  if (!result.ok) {
427
427
  console.error(` frame capture failed: ${result.error ?? 'unknown error'}`)
428
- rnxExit(1)
428
+ peachExit(1)
429
429
  }
430
430
  frames = result.frames ?? []
431
431
  break
432
432
  }
433
433
  if (Date.now() >= deadline) {
434
434
  console.error(' frame capture timed out')
435
- rnxExit(1)
435
+ peachExit(1)
436
436
  }
437
437
  await new Promise((r) => setTimeout(r, 100))
438
438
  }
@@ -459,7 +459,7 @@ export async function runRecord(args: string[], opts: RecordOptions) {
459
459
  )
460
460
  if (!result) {
461
461
  console.error(' gif capture returned no frames')
462
- rnxExit(1)
462
+ peachExit(1)
463
463
  }
464
464
  writeFileSync(out, Buffer.from(result.data, 'base64'))
465
465
  console.log(` saved: ${out} (${formatBytes(result.size)})`)
@@ -505,7 +505,7 @@ export async function runRecord(args: string[], opts: RecordOptions) {
505
505
  ' `peach close --sim <id>` then reopen the sim.',
506
506
  )
507
507
  }
508
- rnxExit(1)
508
+ peachExit(1)
509
509
  }
510
510
 
511
511
  console.log(` recording ${format} for ${durationSec}s → ${out}`)
@@ -526,12 +526,12 @@ export async function runRecord(args: string[], opts: RecordOptions) {
526
526
  )
527
527
  if (!stopResult.ok) {
528
528
  console.error(` stop failed: ${stopResult.error ?? 'unknown error'}`)
529
- rnxExit(1)
529
+ peachExit(1)
530
530
  }
531
531
 
532
532
  if (!stopResult.size) {
533
533
  console.error(' recorder returned an empty blob — nothing written')
534
- rnxExit(1)
534
+ peachExit(1)
535
535
  }
536
536
 
537
537
  await downloadBlob(bridge, out)
@@ -566,7 +566,7 @@ async function probeSim(bridge: WsBridge, code: string): Promise<boolean> {
566
566
  ' loaded an app (or its page is unresponsive). run `peach list`\n' +
567
567
  ' and target a sim with a loaded app via --sim <id>.',
568
568
  )
569
- rnxExit(1)
569
+ peachExit(1)
570
570
  }
571
571
  throw err
572
572
  }
@@ -578,7 +578,7 @@ async function assertRecorderAvailable(bridge: WsBridge) {
578
578
  console.error(
579
579
  ' window.__sootsimRecorder missing — is peach engine running in this sim?',
580
580
  )
581
- rnxExit(1)
581
+ peachExit(1)
582
582
  }
583
583
  }
584
584
 
@@ -600,7 +600,7 @@ async function assertTabVisibleForCapture(bridge: WsBridge) {
600
600
  ' bring the chrome window to the foreground (un-minimize / switch to\n' +
601
601
  ' its tab) or run the recording in headless playwright, then retry.',
602
602
  )
603
- rnxExit(1)
603
+ peachExit(1)
604
604
  }
605
605
  }
606
606
 
@@ -695,7 +695,7 @@ function parseMode(raw: string | undefined): RecordMode {
695
695
  if (!raw) return 'video'
696
696
  if (raw === 'video' || raw === 'live' || raw === 'combined') return raw
697
697
  console.error(` invalid --mode "${raw}" — expected video | live | combined`)
698
- rnxExit(1)
698
+ peachExit(1)
699
699
  }
700
700
 
701
701
  function recordingStatePath(): string {
@@ -770,7 +770,7 @@ async function refuseLiveRecording(parsed: ParsedBridgeCliArgs) {
770
770
  console.error(
771
771
  ` recording already in progress (started ${existing.startedAt}, sim ${existing.simId ?? '?'}). run \`peach record stop\` first, or \`peach record cancel\` to discard.`,
772
772
  )
773
- rnxExit(1)
773
+ peachExit(1)
774
774
  }
775
775
 
776
776
  async function recordStart(args: string[], opts: RecordOptions) {
@@ -800,7 +800,7 @@ async function recordStart(args: string[], opts: RecordOptions) {
800
800
  console.error(
801
801
  ` record start only supports webm or mp4 (got: ${formatArg}). for gif/png use atomic mode: peach record --format ${formatArg} --duration <s>`,
802
802
  )
803
- rnxExit(1)
803
+ peachExit(1)
804
804
  }
805
805
  const fps = Number(valueOf(args, '--fps') ?? '30')
806
806
  const repoScope = readRepoScope(args)
@@ -844,7 +844,7 @@ async function recordStart(args: string[], opts: RecordOptions) {
844
844
  ` start failed: recording store refused to start (${mode})` +
845
845
  (started.error ? `: ${started.error}` : ''),
846
846
  )
847
- rnxExit(1)
847
+ peachExit(1)
848
848
  }
849
849
  } else {
850
850
  await assertRecorderAvailable(bridge)
@@ -852,7 +852,7 @@ async function recordStart(args: string[], opts: RecordOptions) {
852
852
  const lockstep = args.includes('--lockstep')
853
853
  if (lockstep && format !== 'mp4') {
854
854
  console.error(' --lockstep requires --format mp4 (webm is realtime-clocked)')
855
- rnxExit(1)
855
+ peachExit(1)
856
856
  }
857
857
  const startOpts: {
858
858
  format: string
@@ -868,7 +868,7 @@ async function recordStart(args: string[], opts: RecordOptions) {
868
868
  })
869
869
  if (!result.ok) {
870
870
  console.error(` start failed: ${result.error ?? 'unknown error'}`)
871
- rnxExit(1)
871
+ peachExit(1)
872
872
  }
873
873
  }
874
874
  writeRecordingState({
@@ -927,7 +927,7 @@ async function recordPreludeStart(args: string[], opts: RecordOptions) {
927
927
  typeof reason === 'string' && reason ? ` (${reason})` : ''
928
928
  }`,
929
929
  )
930
- rnxExit(1)
930
+ peachExit(1)
931
931
  }
932
932
  console.log(
933
933
  ' recording preview prelude — run `peach record start --mode combined` when the visible proof segment is ready',
@@ -1153,7 +1153,7 @@ async function recordStop(args: string[], opts: RecordOptions) {
1153
1153
  const state = readRecordingState()
1154
1154
  if (!state) {
1155
1155
  console.error(' no recording in progress. start one with `peach record start`.')
1156
- rnxExit(1)
1156
+ peachExit(1)
1157
1157
  }
1158
1158
 
1159
1159
  const parsed = parseBridgeCliArgs(args, {
@@ -1200,12 +1200,12 @@ async function recordStop(args: string[], opts: RecordOptions) {
1200
1200
  if (!stopResult.ok) {
1201
1201
  console.error(` stop failed: ${stopResult.error ?? 'unknown error'}`)
1202
1202
  clearRecordingState()
1203
- rnxExit(1)
1203
+ peachExit(1)
1204
1204
  }
1205
1205
  if (!stopResult.size) {
1206
1206
  console.error(' recorder returned an empty blob — nothing written')
1207
1207
  clearRecordingState()
1208
- rnxExit(1)
1208
+ peachExit(1)
1209
1209
  }
1210
1210
  await downloadBlob(bridge, out)
1211
1211
  clearRecordingState()
@@ -1232,7 +1232,7 @@ async function assertStoreBridgesAvailable(bridge: WsBridge) {
1232
1232
  console.error(
1233
1233
  ' SootSim.bridges.startRecording missing — is peach engine running in this sim?',
1234
1234
  )
1235
- rnxExit(1)
1235
+ peachExit(1)
1236
1236
  }
1237
1237
  }
1238
1238
 
@@ -1266,7 +1266,7 @@ async function assertGuestBundleLoaded(bridge: WsBridge, mode: RecordMode) {
1266
1266
  ` photos/settings/home has none). open a metro/guest app first, or\n` +
1267
1267
  ` use \`--mode video\` for a local screen recording with no upload.`,
1268
1268
  )
1269
- rnxExit(1)
1269
+ peachExit(1)
1270
1270
  }
1271
1271
  }
1272
1272
 
@@ -1329,7 +1329,7 @@ async function injectPreviewSessionAuth(
1329
1329
  ' build without CLI-injected preview auth. reload the sim or open a\n' +
1330
1330
  ' fresh one (`peach open --new`); if it persists, update peach.',
1331
1331
  )
1332
- rnxExit(1)
1332
+ peachExit(1)
1333
1333
  }
1334
1334
  }
1335
1335
 
@@ -1404,11 +1404,11 @@ function handleStoreUploadResult(
1404
1404
  ) {
1405
1405
  if (result.uploadError) {
1406
1406
  console.error(` upload failed: ${result.uploadError}`)
1407
- rnxExit(1)
1407
+ peachExit(1)
1408
1408
  }
1409
1409
  if (!result.previewUrl) {
1410
1410
  console.error(' upload returned no preview URL')
1411
- rnxExit(1)
1411
+ peachExit(1)
1412
1412
  }
1413
1413
  console.log(` preview: ${result.previewUrl}`)
1414
1414
  // the preview id is content-addressed (shareId = hash of the bundle), so
@@ -27,7 +27,7 @@ import {
27
27
  rnxRuntime,
28
28
  } from '../../src/runtime-delivery'
29
29
  import { resolveRNXAppConfig } from '../app-config'
30
- import { rnxExit } from '../run-rnx'
30
+ import { peachExit } from '../run-rnx'
31
31
  import { fetchRuntimeReleaseNotes, formatRuntimeReleaseNotes } from '../runtime-notes'
32
32
  import { resolveDefaultBridgePort } from '../ws-bridge'
33
33
 
@@ -57,7 +57,7 @@ export async function runRuntime(args: string[], opts: RuntimeOptions = {}) {
57
57
  default:
58
58
  console.error(` unknown runtime subcommand: ${sub}`)
59
59
  printHelp()
60
- rnxExit(1)
60
+ peachExit(1)
61
61
  }
62
62
  }
63
63
 
@@ -121,7 +121,7 @@ async function runtimeInstall(args: string[], opts: RuntimeOptions) {
121
121
  return result
122
122
  } catch (err) {
123
123
  console.error(` ${describeError(err)}`)
124
- rnxExit(1)
124
+ peachExit(1)
125
125
  }
126
126
  }
127
127
 
@@ -235,14 +235,14 @@ async function runtimeUse(args: string[]) {
235
235
  const version = args[0]
236
236
  if (!version) {
237
237
  console.error(` usage: peach runtime use <version>`)
238
- rnxExit(1)
238
+ peachExit(1)
239
239
  }
240
240
  const installed = rnxRuntime.listInstalled()
241
241
  if (!installed.includes(version)) {
242
242
  console.error(` version ${version} is not installed`)
243
243
  console.error(` installed: ${installed.join(', ') || '(none)'}`)
244
244
  console.error(` run \`peach runtime install ${version}\` first`)
245
- rnxExit(1)
245
+ peachExit(1)
246
246
  }
247
247
  await activateVersion(version)
248
248
  }
@@ -253,7 +253,7 @@ async function runtimeRemove(args: string[]) {
253
253
  const version = args[0]
254
254
  if (!version) {
255
255
  console.error(` usage: peach runtime remove <version>`)
256
- rnxExit(1)
256
+ peachExit(1)
257
257
  }
258
258
  const active = readActiveRuntime()
259
259
  if (active === version) {
@@ -261,11 +261,11 @@ async function runtimeRemove(args: string[]) {
261
261
  console.error(
262
262
  ` switch with \`peach runtime use <other>\` first, or install another version`,
263
263
  )
264
- rnxExit(1)
264
+ peachExit(1)
265
265
  }
266
266
  if (readLiveRuntimeVersions().includes(version)) {
267
267
  console.error(` cannot remove runtime ${version} while a project is using it`)
268
- rnxExit(1)
268
+ peachExit(1)
269
269
  }
270
270
  const dir = runtimeDir(version)
271
271
  if (!fs.existsSync(dir)) {
@@ -292,7 +292,7 @@ async function runtimeWhich() {
292
292
  async function runtimeNotes(args: string[]) {
293
293
  if (args.length > 0) {
294
294
  console.error(` usage: peach runtime notes`)
295
- rnxExit(1)
295
+ peachExit(1)
296
296
  }
297
297
  const { config } = await resolveRNXAppConfig()
298
298
  const version = config?.runtimeVersion ?? readActiveRuntime()
@@ -11,7 +11,7 @@ import { mkdirSync, writeFileSync } from 'fs'
11
11
  import { resolve } from 'path'
12
12
  import { devices } from 'peach-engine/settings/devices'
13
13
  import { buildResolveRectEval, type SampleRect } from '../browser-evals'
14
- import { rnxExit } from '../run-rnx'
14
+ import { peachExit } from '../run-rnx'
15
15
  import { callInBridge, createBridgeFromParsed, parseBridgeCliArgs } from '../ws-bridge'
16
16
  import { inspectCaptureReady, waitReadyReason } from './inspect/core'
17
17
 
@@ -41,13 +41,13 @@ output:
41
41
  layer-<id>.png each subtree rendered alone on a transparent frame
42
42
  manifest.json device metrics + logical rect per layer
43
43
  `)
44
- rnxExit(0)
44
+ peachExit(0)
45
45
  }
46
46
 
47
47
  const idsArg = args.find((_, i) => args[i - 1] === '--ids')
48
48
  if (!idsArg) {
49
49
  console.error(' --ids is required (comma-separated testIDs)')
50
- rnxExit(1)
50
+ peachExit(1)
51
51
  }
52
52
  const testIds = idsArg
53
53
  .split(',')
@@ -55,7 +55,7 @@ output:
55
55
  .filter(Boolean)
56
56
  if (testIds.length === 0) {
57
57
  console.error(' --ids resolved to an empty list')
58
- rnxExit(1)
58
+ peachExit(1)
59
59
  }
60
60
  const outDirArg = args.find((_, i) => args[i - 1] === '--out-dir')
61
61
  const outDir = resolve(process.cwd(), outDirArg ?? '/tmp/peach-layers')
@@ -6,7 +6,7 @@
6
6
  // wraps the existing `toggle-screenshot-mode` shell action so the CLI
7
7
  // stays behavior-compatible with the menu item / keyboard shortcut.
8
8
 
9
- import { rnxExit } from '../run-rnx'
9
+ import { peachExit } from '../run-rnx'
10
10
  import { runShellBooleanMode, type ShellBooleanModeOptions } from './shell-boolean-mode'
11
11
 
12
12
  export async function runScreenshotMode(args: string[], opts: ShellBooleanModeOptions) {
@@ -27,7 +27,7 @@ examples:
27
27
  peach mode screenshot on
28
28
  peach mode screenshot off
29
29
  `)
30
- rnxExit(0)
30
+ peachExit(0)
31
31
  }
32
32
 
33
33
  await runShellBooleanMode(args, opts, {