peachsim 0.1.940 → 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 +981 -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 +670 -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 +304 -25
  133. package/dist-lib/native-app-identity.mjs +302 -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 +367 -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
@@ -491,6 +491,7 @@ Writes a page profile, worker profiles, and a manifest alongside the output.
491
491
  const response = await cdp.send('Runtime.evaluate', {
492
492
  expression,
493
493
  returnByValue: true,
494
+ awaitPromise: true,
494
495
  })
495
496
  if (response?.exceptionDetails) {
496
497
  throw new Error(
@@ -693,8 +694,33 @@ Writes a page profile, worker profiles, and a manifest alongside the output.
693
694
  captureStarted = true
694
695
  startPromises.push(startTarget(pageTarget))
695
696
  for (const target of workers.values()) startPromises.push(startTarget(target))
697
+ // with the native host attached, the engine's workers are realm threads in the host, reached
698
+ // through the page; they record over the same window as the browser targets
699
+ const hostRealms =
700
+ (await evaluatePage(`typeof window.__peachNative?.cpuProfile === 'function'`)) ===
701
+ true
702
+ const hostTargets: AttachedTarget[] = []
703
+ if (hostRealms) {
704
+ const startedAtMs = performance.now()
705
+ const realms = await evaluatePage(
706
+ `window.__peachNative.cpuProfile('start', ${sampleInterval * 1000})`,
707
+ )
708
+ for (const realm of Array.isArray(realms) ? realms : []) {
709
+ hostTargets.push({
710
+ attachedAtMs: startedAtMs,
711
+ detached: false,
712
+ error: realm.error,
713
+ profileStartedAtMs: realm.error ? undefined : performance.now(),
714
+ role: cpuProfileTargetRole(realm.url, realm.name),
715
+ started: !realm.error,
716
+ title: realm.name,
717
+ type: 'host-realm',
718
+ url: realm.url,
719
+ })
720
+ }
721
+ }
696
722
  await waitForStableTargetSetup()
697
- const startedCount = [pageTarget, ...workers.values()].filter(
723
+ const startedCount = [pageTarget, ...workers.values(), ...hostTargets].filter(
698
724
  (target) => target.started,
699
725
  ).length
700
726
  if (startedCount === 0) {
@@ -737,6 +763,23 @@ Writes a page profile, worker profiles, and a manifest alongside the output.
737
763
  await cdp.send('Profiler.disable', {}, target.sessionId).catch(() => {})
738
764
  }),
739
765
  )
766
+ if (hostRealms) {
767
+ const stopped = await evaluatePage(`window.__peachNative.cpuProfile('stop', 0)`)
768
+ const stoppedAtMs = performance.now()
769
+ for (const realm of Array.isArray(stopped) ? stopped : []) {
770
+ const target = hostTargets.find(
771
+ (candidate) =>
772
+ candidate.title === realm.name &&
773
+ candidate.url === realm.url &&
774
+ !candidate.profile,
775
+ )
776
+ if (!target) continue
777
+ if (realm.profile) target.profile = realm.profile
778
+ else target.error = realm.error
779
+ target.profileStoppedAtMs = stoppedAtMs
780
+ }
781
+ results.push(...hostTargets)
782
+ }
740
783
  // querying the page while five high-frequency profilers are still active
741
784
  // can itself time out and lengthen the requested sampling interval. stop
742
785
  // first, then read the already-recorded workload completion evidence. a
@@ -945,6 +988,7 @@ export function cpuProfileTargetRole(url: string, title = ''): string {
945
988
  return 'compositor'
946
989
  }
947
990
  if (identity.includes('shell-worker')) return 'shell'
991
+ if (identity.includes('peach-host')) return 'host'
948
992
  if (identity.includes('named-worklet')) return 'worklet'
949
993
  if (identity.includes('render-worker') || identity.includes('tenant-worker')) {
950
994
  return 'tenant'
@@ -19,7 +19,9 @@ import { spawnSync } from 'child_process'
19
19
  import {
20
20
  existsSync,
21
21
  mkdirSync,
22
+ readdirSync,
22
23
  readFileSync,
24
+ readlinkSync,
23
25
  realpathSync,
24
26
  rmSync,
25
27
  writeFileSync,
@@ -27,6 +29,7 @@ import {
27
29
  import { homedir } from 'os'
28
30
  import { dirname, resolve } from 'path'
29
31
  import { DEFAULT_SOOTSIM_BRIDGE_PORT } from '../../src/bridge-constants'
32
+ import { unregisterNativeDesktopLinks } from '../../src/desktop-host-delivery'
30
33
  import {
31
34
  daemonAppDir,
32
35
  isDaemonLockfileFresh,
@@ -37,7 +40,7 @@ import {
37
40
  import { rnxPublicBrand } from '../../src/public-brand'
38
41
  import { printCommandHelp } from '../help'
39
42
  import { escapeXml } from '../maestro-report'
40
- import { rnxExit } from '../run-rnx'
43
+ import { peachExit } from '../run-rnx'
41
44
  import { rnxSelfInvocation, type RnxSelfInvocation } from '../self-invocation'
42
45
  import { DAEMON_ENVIRONMENT, ensureDaemonApp } from './daemon-mac-app'
43
46
 
@@ -84,7 +87,7 @@ export async function runDaemon(args: string[], opts: DaemonOptions = {}) {
84
87
  default:
85
88
  console.error(` unknown daemon subcommand: ${sub}`)
86
89
  printHelp()
87
- rnxExit(1)
90
+ peachExit(1)
88
91
  }
89
92
  }
90
93
 
@@ -316,7 +319,7 @@ export async function daemonInstall({ port, force }: { port: number; force: bool
316
319
 
317
320
  console.error(` background daemon is not supported on ${process.platform}`)
318
321
  console.error(` run 'peach serve' in a persistent shell instead`)
319
- rnxExit(1)
322
+ peachExit(1)
320
323
  }
321
324
 
322
325
  function macServiceTarget(): string {
@@ -364,9 +367,9 @@ function macBridgeHasListener(pid: number, port: number): boolean {
364
367
 
365
368
  export async function stopMacDaemonBridge(
366
369
  invocation: RnxSelfInvocation,
367
- target = macServiceTarget(),
370
+ target: string | null = macServiceTarget(),
368
371
  ): Promise<void> {
369
- const service = readMacDaemonState(target)
372
+ const service = target === null ? null : readMacDaemonState(target)
370
373
  const lock = readDaemonLockfile()
371
374
  if (isDaemonLockfileFresh(lock) && lock.pid !== service?.pid) {
372
375
  // a lockfile alone cannot authorize a signal after pid reuse.
@@ -387,7 +390,7 @@ export async function stopMacDaemonBridge(
387
390
  }
388
391
  process.kill(lock.pid, 'SIGTERM')
389
392
  }
390
- if (service) runLaunchctl(['bootout', target])
393
+ if (service && target !== null) runLaunchctl(['bootout', target])
391
394
  if (!lock) return
392
395
  const deadline = Date.now() + 4000
393
396
  while (Date.now() < deadline) {
@@ -436,6 +439,102 @@ export async function startMacDaemon(
436
439
  )
437
440
  }
438
441
 
442
+ // --- linux bridge ownership --------------------------------------------
443
+
444
+ // the linux counterparts to the mac listener and command checks above. both
445
+ // read /proc directly instead of shelling out to lsof, ss or ps, so minimal
446
+ // containers and fresh users get the same ownership proof with no extra
447
+ // binaries installed.
448
+
449
+ function linuxBridgeHasListener(pid: number, port: number): boolean {
450
+ let fds: string[]
451
+ try {
452
+ fds = readdirSync(`/proc/${pid}/fd`)
453
+ } catch {
454
+ return false
455
+ }
456
+ const sockets = new Set<string>()
457
+ for (const fd of fds) {
458
+ try {
459
+ const inode = readlinkSync(`/proc/${pid}/fd/${fd}`).match(/^socket:\[(\d+)\]$/)?.[1]
460
+ if (inode) sockets.add(inode)
461
+ } catch {
462
+ // the fd closed between listing and read; skip it.
463
+ }
464
+ }
465
+ if (sockets.size === 0) return false
466
+ const hexPort = port.toString(16).toUpperCase().padStart(4, '0')
467
+ for (const table of ['/proc/net/tcp', '/proc/net/tcp6']) {
468
+ let body: string
469
+ try {
470
+ body = readFileSync(table, 'utf8')
471
+ } catch {
472
+ continue
473
+ }
474
+ const lines = body.split('\n')
475
+ for (let i = 1; i < lines.length; i++) {
476
+ const cols = lines[i].trim().split(/\s+/)
477
+ if (cols.length < 10) continue
478
+ if (cols[3] !== '0A') continue
479
+ if (!cols[1].endsWith(`:${hexPort}`)) continue
480
+ if (sockets.has(cols[9])) return true
481
+ }
482
+ }
483
+ return false
484
+ }
485
+
486
+ function readLinuxProcessCommand(pid: number): string | null {
487
+ // /proc cmdline is the argv vector verbatim; joining with spaces matches
488
+ // what `ps -o command=` prints without needing procps installed.
489
+ try {
490
+ const args = readFileSync(`/proc/${pid}/cmdline`, 'utf8')
491
+ .split('\0')
492
+ .filter((part) => part.length > 0)
493
+ return args.length > 0 ? args.join(' ') : null
494
+ } catch {
495
+ return null
496
+ }
497
+ }
498
+
499
+ export async function stopLinuxDaemonBridge(
500
+ invocation: RnxSelfInvocation,
501
+ registered: boolean,
502
+ ): Promise<void> {
503
+ if (registered) {
504
+ const res = spawnSync('systemctl', ['--user', 'stop', SERVICE_NAME_LINUX], {
505
+ stdio: 'pipe',
506
+ encoding: 'utf8',
507
+ })
508
+ if (res.status !== 0) {
509
+ console.error(` stop failed: ${res.stderr?.trim()}`)
510
+ peachExit(1)
511
+ }
512
+ return
513
+ }
514
+ const lock = readDaemonLockfile()
515
+ if (!isDaemonLockfileFresh(lock)) return
516
+ // a lockfile alone cannot authorize a signal after pid reuse.
517
+ const ownsListener = linuxBridgeHasListener(lock.pid, lock.bridgePort)
518
+ const runningCommand = readLinuxProcessCommand(lock.pid)
519
+ const command = [invocation.executable, ...invocation.prefixArgs, 'serve'].join(' ')
520
+ if (
521
+ !ownsListener ||
522
+ runningCommand === null ||
523
+ (runningCommand !== command && !runningCommand.startsWith(`${command} `))
524
+ ) {
525
+ throw new Error(
526
+ `cannot hand off bridge on port ${lock.bridgePort}: pid ${lock.pid} is not this Peach invocation`,
527
+ )
528
+ }
529
+ process.kill(lock.pid, 'SIGTERM')
530
+ const deadline = Date.now() + 4000
531
+ while (Date.now() < deadline) {
532
+ if (!linuxBridgeHasListener(lock.pid, lock.bridgePort)) return
533
+ await new Promise((resolve) => setTimeout(resolve, 100))
534
+ }
535
+ throw new Error(`bridge pid ${lock.pid} did not release port ${lock.bridgePort}`)
536
+ }
537
+
439
538
  // removed-path log (idempotent helpers track what they actually touched so the
440
539
  // caller can report a unified "not installed" / "removed N entries" line).
441
540
  type TeardownReport = { removed: string[]; missing: boolean }
@@ -542,6 +641,7 @@ export function teardownDaemonService(): TeardownReport {
542
641
  export function teardownRnxHome(): TeardownReport {
543
642
  const removed: string[] = []
544
643
  const home = rnxHomeDir()
644
+ unregisterNativeDesktopLinks()
545
645
  if (existsSync(home)) {
546
646
  rmSync(home, { recursive: true, force: true })
547
647
  removed.push(home)
@@ -558,7 +658,7 @@ async function daemonUninstall(rest: string[]) {
558
658
  if (unknown.length > 0) {
559
659
  console.error(` unknown daemon uninstall option: ${unknown[0]}`)
560
660
  printHelp()
561
- rnxExit(1)
661
+ peachExit(1)
562
662
  return
563
663
  }
564
664
  const purge = rest.includes('--purge')
@@ -641,7 +741,7 @@ async function daemonStatus() {
641
741
  return
642
742
  }
643
743
  console.error(` unsupported platform: ${process.platform}`)
644
- rnxExit(1)
744
+ peachExit(1)
645
745
  }
646
746
 
647
747
  async function daemonRestart() {
@@ -649,7 +749,7 @@ async function daemonRestart() {
649
749
  const path = macPlistPath()
650
750
  if (!existsSync(path)) {
651
751
  console.error(' daemon not registered. run `peach daemon install`')
652
- rnxExit(1)
752
+ peachExit(1)
653
753
  }
654
754
  await stopMacDaemonBridge(resolveDaemonInvocation())
655
755
  await startMacDaemon(path)
@@ -660,7 +760,7 @@ async function daemonRestart() {
660
760
  const path = linuxUnitPath()
661
761
  if (!existsSync(path)) {
662
762
  console.error(' daemon not registered. run `peach daemon install`')
663
- rnxExit(1)
763
+ peachExit(1)
664
764
  }
665
765
  const res = spawnSync('systemctl', ['--user', 'restart', SERVICE_NAME_LINUX], {
666
766
  stdio: 'pipe',
@@ -668,13 +768,13 @@ async function daemonRestart() {
668
768
  })
669
769
  if (res.status !== 0) {
670
770
  console.error(` restart failed: ${res.stderr?.trim()}`)
671
- rnxExit(1)
771
+ peachExit(1)
672
772
  }
673
773
  console.log(' restarted')
674
774
  return
675
775
  }
676
776
  console.error(` unsupported platform: ${process.platform}`)
677
- rnxExit(1)
777
+ peachExit(1)
678
778
  }
679
779
 
680
780
  async function daemonStart() {
@@ -682,7 +782,7 @@ async function daemonStart() {
682
782
  const path = macPlistPath()
683
783
  if (!existsSync(path)) {
684
784
  console.error(' daemon not registered. run `peach daemon install`')
685
- rnxExit(1)
785
+ peachExit(1)
686
786
  }
687
787
  await startMacDaemon(path)
688
788
  console.log(' started')
@@ -692,7 +792,7 @@ async function daemonStart() {
692
792
  const path = linuxUnitPath()
693
793
  if (!existsSync(path)) {
694
794
  console.error(' daemon not registered. run `peach daemon install`')
695
- rnxExit(1)
795
+ peachExit(1)
696
796
  }
697
797
  const res = spawnSync('systemctl', ['--user', 'start', SERVICE_NAME_LINUX], {
698
798
  stdio: 'pipe',
@@ -700,45 +800,43 @@ async function daemonStart() {
700
800
  })
701
801
  if (res.status !== 0) {
702
802
  console.error(` start failed: ${res.stderr?.trim()}`)
703
- rnxExit(1)
803
+ peachExit(1)
704
804
  }
705
805
  console.log(' started')
706
806
  return
707
807
  }
708
808
  console.error(` unsupported platform: ${process.platform}`)
709
- rnxExit(1)
809
+ peachExit(1)
710
810
  }
711
811
 
712
812
  async function daemonStop() {
713
813
  if (process.platform === 'darwin') {
714
814
  const path = macPlistPath()
715
- if (!existsSync(path)) {
815
+ const registered = existsSync(path)
816
+ if (!registered && !isDaemonLockfileFresh(readDaemonLockfile())) {
716
817
  console.error(' daemon not registered — nothing to stop')
717
- rnxExit(1)
818
+ peachExit(1)
718
819
  }
719
- await stopMacDaemonBridge(resolveDaemonInvocation())
820
+ await stopMacDaemonBridge(
821
+ resolveDaemonInvocation(),
822
+ registered ? macServiceTarget() : null,
823
+ )
720
824
  console.log(' stopped')
721
825
  return
722
826
  }
723
827
  if (process.platform === 'linux') {
724
828
  const path = linuxUnitPath()
725
- if (!existsSync(path)) {
829
+ const registered = existsSync(path)
830
+ if (!registered && !isDaemonLockfileFresh(readDaemonLockfile())) {
726
831
  console.error(' daemon not registered — nothing to stop')
727
- rnxExit(1)
728
- }
729
- const res = spawnSync('systemctl', ['--user', 'stop', SERVICE_NAME_LINUX], {
730
- stdio: 'pipe',
731
- encoding: 'utf8',
732
- })
733
- if (res.status !== 0) {
734
- console.error(` stop failed: ${res.stderr?.trim()}`)
735
- rnxExit(1)
832
+ peachExit(1)
736
833
  }
834
+ await stopLinuxDaemonBridge(resolveDaemonInvocation(), registered)
737
835
  console.log(' stopped')
738
836
  return
739
837
  }
740
838
  console.error(` unsupported platform: ${process.platform}`)
741
- rnxExit(1)
839
+ peachExit(1)
742
840
  }
743
841
 
744
842
  // tail the daemon's logs. macOS reads the launchd-redirected files directly
@@ -758,7 +856,7 @@ async function daemonLogs(rest: string[]) {
758
856
  const present = [out, err].filter((p) => existsSync(p))
759
857
  if (present.length === 0) {
760
858
  console.error(` no daemon logs yet at ${LOG_DIR}. run \`peach daemon install\``)
761
- rnxExit(1)
859
+ peachExit(1)
762
860
  }
763
861
  const args = follow
764
862
  ? ['-n', String(lines), '-F', ...present]
@@ -798,11 +896,11 @@ async function daemonLogs(rest: string[]) {
798
896
  ` could not read --user journal for ${unit} (this user isn't in the systemd-journal group yet).\n` +
799
897
  ` run: sudo journalctl _SYSTEMD_USER_UNIT=${unit} -n ${lines} --no-pager`,
800
898
  )
801
- rnxExit(1)
899
+ peachExit(1)
802
900
  }
803
901
 
804
902
  console.error(` unsupported platform: ${process.platform}`)
805
- rnxExit(1)
903
+ peachExit(1)
806
904
  }
807
905
 
808
906
  function readPlistBinary(path: string): string | null {
@@ -20,7 +20,7 @@
20
20
 
21
21
  import { DEFAULT_SOOTSIM_BRIDGE_PORT } from '../../src/bridge-constants'
22
22
  import { printBridgeFailureDiagnostics } from '../bridge-diagnostics'
23
- import { rethrowIfExit, rnxExit } from '../run-rnx'
23
+ import { rethrowIfExit, peachExit } from '../run-rnx'
24
24
  import {
25
25
  createBridgeFromParsed,
26
26
  evalInBridge,
@@ -383,7 +383,7 @@ export async function runDebug(args: string[], opts: DebugOptions) {
383
383
 
384
384
  if (!subcommand || args.includes('--help') || args.includes('-h')) {
385
385
  printHelp()
386
- rnxExit(0)
386
+ peachExit(0)
387
387
  }
388
388
 
389
389
  const wsPort = parsed.wsPort
@@ -398,7 +398,14 @@ export async function runDebug(args: string[], opts: DebugOptions) {
398
398
  // implementation lives in inspect.ts. forward to runInspect with the raw
399
399
  // `debug` prefix intact — runInspect strips it. done before we open a
400
400
  // debug-side bridge so we don't hold two bridges at once.
401
- const INSPECT_FORWARDED = new Set(['state', 'js', 'perf', 'memory', 'sample-color'])
401
+ const INSPECT_FORWARDED = new Set([
402
+ 'state',
403
+ 'js',
404
+ 'eval',
405
+ 'perf',
406
+ 'memory',
407
+ 'sample-color',
408
+ ])
402
409
  if (INSPECT_FORWARDED.has(sub)) {
403
410
  const { runInspect } = await import('./inspect')
404
411
  // pass the raw arg list through so every flag (--sim, --json,
@@ -422,7 +429,7 @@ export async function runDebug(args: string[], opts: DebugOptions) {
422
429
  console.error(
423
430
  ` usage: peach debug enable <channel[,channel,...]>\n known: ${KNOWN_CHANNELS.join(', ')}`,
424
431
  )
425
- rnxExit(1)
432
+ peachExit(1)
426
433
  }
427
434
  const active = await setDebugChannels(
428
435
  bridge,
@@ -450,7 +457,7 @@ export async function runDebug(args: string[], opts: DebugOptions) {
450
457
  const channel = rest[0]
451
458
  if (!channel) {
452
459
  console.error(' usage: peach debug toggle <channel>')
453
- rnxExit(1)
460
+ peachExit(1)
454
461
  }
455
462
  const result = await callDebugBridge(
456
463
  bridge,
@@ -518,7 +525,7 @@ export async function runDebug(args: string[], opts: DebugOptions) {
518
525
  const b = rest[1]
519
526
  if (!a || !b) {
520
527
  console.error(' usage: peach debug diff <labelA> <labelB>')
521
- rnxExit(1)
528
+ peachExit(1)
522
529
  }
523
530
  // Maps don't serialize — project added/removed/changed arrays only
524
531
  const result = await callDebugBridge(
@@ -563,7 +570,7 @@ export async function runDebug(args: string[], opts: DebugOptions) {
563
570
  ' zero draws means either nothing on screen is an image, or the audit\n' +
564
571
  ' is not attached to the surface that paints.',
565
572
  )
566
- rnxExit(2)
573
+ peachExit(2)
567
574
  }
568
575
  const under = report.rows.filter(
569
576
  (row) => row.shortfall !== null && row.shortfall < minimum,
@@ -573,7 +580,7 @@ export async function runDebug(args: string[], opts: DebugOptions) {
573
580
  } else {
574
581
  printImageAudit(report, args.includes('--all') ? report.rows : under, minimum)
575
582
  }
576
- if (under.length > 0) rnxExit(1)
583
+ if (under.length > 0) peachExit(1)
577
584
  break
578
585
  }
579
586
 
@@ -584,7 +591,7 @@ export async function runDebug(args: string[], opts: DebugOptions) {
584
591
  console.log(fmt(result, pretty))
585
592
  } else {
586
593
  console.error(' usage: peach debug find <sheets|portals|boundaries>')
587
- rnxExit(1)
594
+ peachExit(1)
588
595
  }
589
596
  break
590
597
  }
@@ -602,7 +609,7 @@ export async function runDebug(args: string[], opts: DebugOptions) {
602
609
  console.error(
603
610
  ' usage: peach debug trace anim <on|off|status|clear|<id>> [id|limit]',
604
611
  )
605
- rnxExit(1)
612
+ peachExit(1)
606
613
  }
607
614
  if (sub === 'on') {
608
615
  const raw = rest[2] ?? 'all'
@@ -610,7 +617,7 @@ export async function runDebug(args: string[], opts: DebugOptions) {
610
617
  const target: number | 'all' = raw === 'all' ? 'all' : Number(raw)
611
618
  if (target !== 'all' && !Number.isFinite(target)) {
612
619
  console.error(` invalid target: ${raw}`)
613
- rnxExit(1)
620
+ peachExit(1)
614
621
  }
615
622
  await callTestBridge(bridge, 'enableAnimationTrace', target, limit)
616
623
  const listed = await callTestBridge(bridge, 'listAnimationTraces')
@@ -622,7 +629,7 @@ export async function runDebug(args: string[], opts: DebugOptions) {
622
629
  const target: number | 'all' = raw === 'all' ? 'all' : Number(raw)
623
630
  if (target !== 'all' && !Number.isFinite(target)) {
624
631
  console.error(` invalid target: ${raw}`)
625
- rnxExit(1)
632
+ peachExit(1)
626
633
  }
627
634
  await callTestBridge(bridge, 'disableAnimationTrace', target)
628
635
  console.log(fmt({ disabled: target }, pretty))
@@ -639,7 +646,7 @@ export async function runDebug(args: string[], opts: DebugOptions) {
639
646
  const id = Number(sub)
640
647
  if (!Number.isFinite(id)) {
641
648
  console.error(` invalid id: ${sub}`)
642
- rnxExit(1)
649
+ peachExit(1)
643
650
  }
644
651
  const limit = rest[2] ? Number(rest[2]) : undefined
645
652
  const samples = await callTestBridge(bridge, 'getAnimationTrace', id, limit)
@@ -648,7 +655,7 @@ export async function runDebug(args: string[], opts: DebugOptions) {
648
655
  }
649
656
  if (traceTarget !== 'shell') {
650
657
  console.error(' usage: peach debug trace <shell|anim> [args]')
651
- rnxExit(1)
658
+ peachExit(1)
652
659
  }
653
660
  if (!traceCommand || /^[0-9]+$/.test(traceCommand)) {
654
661
  const recentLimit = traceCommand ? Number(traceCommand) : 50
@@ -680,7 +687,7 @@ export async function runDebug(args: string[], opts: DebugOptions) {
680
687
  console.error(
681
688
  ' usage: peach debug trace shell [on [limit]|off|status|clear|recentLimit]',
682
689
  )
683
- rnxExit(1)
690
+ peachExit(1)
684
691
  }
685
692
 
686
693
  case 'record': {
@@ -717,7 +724,7 @@ export async function runDebug(args: string[], opts: DebugOptions) {
717
724
  default:
718
725
  console.error(` unknown subcommand: ${sub}`)
719
726
  printHelp()
720
- rnxExit(1)
727
+ peachExit(1)
721
728
  }
722
729
  } catch (err: any) {
723
730
  rethrowIfExit(err)
@@ -727,7 +734,7 @@ export async function runDebug(args: string[], opts: DebugOptions) {
727
734
  warningsCommand: `peach get warnings 5${simHint}`,
728
735
  requestsCommand: `peach get requests 5${simHint}`,
729
736
  })
730
- rnxExit(1)
737
+ peachExit(1)
731
738
  } finally {
732
739
  bridge.close()
733
740
  }
@@ -1,5 +1,5 @@
1
1
  import { runElectron } from './electron'
2
- import { runInstallDesktop } from './install-desktop'
2
+ import { runInstallDesktop, runUninstallDesktop } from './install-desktop'
3
3
 
4
4
  interface DesktopOptions {
5
5
  port?: number
@@ -13,11 +13,13 @@ peach desktop — launch or install the desktop companion
13
13
  usage:
14
14
  peach desktop [options]
15
15
  peach desktop install [options]
16
+ peach desktop uninstall
16
17
 
17
18
  options:
18
19
  --port <number> connect to a running dev server on this port
19
20
  --profile <id> launch with an isolated persistent storage profile
20
21
  --ephemeral launch with a temporary isolated storage profile
22
+ --electron launch Electron on Windows; native app on Mac and Linux
21
23
  --native launch (macOS arm64) or install (also Linux x64) the native host
22
24
  -y, --yes install without prompting
23
25
  `)
@@ -30,6 +32,10 @@ export async function runDesktop(args: string[], opts: DesktopOptions): Promise<
30
32
  }
31
33
 
32
34
  const sub = args.find((arg) => !arg.startsWith('-'))
35
+ if (sub === 'uninstall') {
36
+ await runUninstallDesktop(args.filter((arg) => arg !== 'uninstall'))
37
+ return
38
+ }
33
39
  if (sub === 'install') {
34
40
  const index = args.indexOf(sub)
35
41
  await runInstallDesktop([...args.slice(0, index), ...args.slice(index + 1)])