peachsim 0.1.1594 → 0.1.1620

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 (226) hide show
  1. package/README.md +34 -11
  2. package/cli/app-catalog.ts +7 -5
  3. package/cli/app-config.ts +6 -2
  4. package/cli/app-fonts.ts +74 -35
  5. package/cli/app-stubs.ts +173 -42
  6. package/cli/bin.ts +9 -0
  7. package/cli/bridge-flow-runner.ts +226 -145
  8. package/cli/cloud-client.ts +52 -29
  9. package/cli/commands/agent-indicator-mode.ts +39 -0
  10. package/cli/commands/box.ts +13 -3
  11. package/cli/commands/control.ts +132 -108
  12. package/cli/commands/daemon.ts +99 -24
  13. package/cli/commands/debug.ts +29 -1
  14. package/cli/commands/detect.ts +1 -1
  15. package/cli/commands/device.ts +6 -8
  16. package/cli/commands/diagnose.ts +2 -0
  17. package/cli/commands/doctor.ts +97 -34
  18. package/cli/commands/flow.ts +22 -12
  19. package/cli/commands/inspect/actions.ts +2 -0
  20. package/cli/commands/inspect/core.ts +106 -40
  21. package/cli/commands/inspect/list.ts +9 -3
  22. package/cli/commands/inspect/memory.ts +9 -0
  23. package/cli/commands/inspect/settling.ts +167 -49
  24. package/cli/commands/inspect/shared.ts +53 -12
  25. package/cli/commands/inspect/wait-event.ts +0 -1
  26. package/cli/commands/inspect/wait.ts +2 -2
  27. package/cli/commands/inspect.ts +19 -4
  28. package/cli/commands/kotlin.ts +5 -7
  29. package/cli/commands/maestro-compare.ts +25 -2
  30. package/cli/commands/mode.ts +13 -1
  31. package/cli/commands/perf-record.ts +296 -0
  32. package/cli/commands/perf.ts +6 -1
  33. package/cli/commands/platform.ts +34 -11
  34. package/cli/commands/preview.ts +1 -1
  35. package/cli/commands/record.ts +48 -20
  36. package/cli/commands/runtime.ts +9 -11
  37. package/cli/commands/screenshots.ts +4 -3
  38. package/cli/commands/setup.ts +17 -5
  39. package/cli/commands/skills.ts +16 -43
  40. package/cli/commands/swift.ts +18 -3
  41. package/cli/commands/upgrade.ts +7 -6
  42. package/cli/commands/upload.ts +27 -51
  43. package/cli/commands/window.ts +449 -0
  44. package/cli/desktop-companion.ts +84 -10
  45. package/cli/drivers/desktop.ts +2 -0
  46. package/cli/drivers/playwright-sim-host.ts +12 -1
  47. package/cli/flow-file.ts +1 -0
  48. package/cli/maestro-compare.ts +19 -3
  49. package/cli/main.ts +22 -1
  50. package/cli/material-symbol-fonts.ts +171 -0
  51. package/cli/outbound-endpoints.ts +32 -16
  52. package/cli/preview-capture.ts +2 -2
  53. package/cli/run-registry.ts +5 -2
  54. package/cli/telemetry.ts +6 -3
  55. package/cli/ws-bridge.ts +15 -19
  56. package/detox/attempt-events.cjs +63 -7
  57. package/detox/attempt-events.d.cts +7 -0
  58. package/detox/gestures.ts +115 -2
  59. package/detox/index.ts +313 -411
  60. package/detox/jest-environment.cjs +86 -0
  61. package/detox/jest-preset.cjs +3 -1
  62. package/detox/jest-setup-after-env.cjs +1 -0
  63. package/detox/matchers.ts +3 -6
  64. package/detox/motion-change.cjs +9 -1
  65. package/detox/proof-frame.cjs +83 -9
  66. package/dist-lib/agent-daemon-client.cjs +1 -1
  67. package/dist-lib/agent-events.cjs +1 -1
  68. package/dist-lib/attached-projects.cjs +1 -1
  69. package/dist-lib/auth/shared-session.cjs +15 -5
  70. package/dist-lib/backend-origin.cjs +1 -1
  71. package/dist-lib/beta.cjs +1 -1
  72. package/dist-lib/beta.mjs +1 -1
  73. package/dist-lib/bridge-constants.cjs +1 -1
  74. package/dist-lib/bridge-contract-input.cjs +1 -1
  75. package/dist-lib/bridge-contract-input.mjs +1 -1
  76. package/dist-lib/bridge-contract.cjs +1 -1
  77. package/dist-lib/bridge-contract.mjs +1 -1
  78. package/dist-lib/bundle-channel.cjs +4005 -1362
  79. package/dist-lib/capture-contract.cjs +1 -1
  80. package/dist-lib/capture-contract.mjs +1 -1
  81. package/dist-lib/cloud-contract.cjs +6 -1
  82. package/dist-lib/cloud-contract.mjs +5 -1
  83. package/dist-lib/cloud-test-args.cjs +1 -1
  84. package/dist-lib/cloud-test-args.mjs +1 -1
  85. package/dist-lib/cloud.cjs +2 -2
  86. package/dist-lib/cloud.mjs +2 -2
  87. package/dist-lib/config.cjs +26 -7
  88. package/dist-lib/config.mjs +26 -7
  89. package/dist-lib/detox/index.cjs +1908 -539
  90. package/dist-lib/detox/jest-environment.cjs +86 -0
  91. package/dist-lib/detox/jest-preset.cjs +3 -1
  92. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  93. package/dist-lib/expo-swift-bundle.cjs +3617 -1016
  94. package/dist-lib/expo-swift-metadata.cjs +1 -1
  95. package/dist-lib/expo-swift-metadata.mjs +1 -1
  96. package/dist-lib/home-paths.cjs +1 -1
  97. package/dist-lib/host/bridge-host.cjs +4760 -1825
  98. package/dist-lib/host/fetch-proxy-handler.cjs +59 -9
  99. package/dist-lib/host/fetch-proxy-overrides.cjs +62 -2
  100. package/dist-lib/host/fetch-proxy-overrides.mjs +56 -2
  101. package/dist-lib/host/replacement-module-handler.cjs +8 -8
  102. package/dist-lib/host/websocket-proxy.cjs +22 -6
  103. package/dist-lib/index.cjs +610 -184
  104. package/dist-lib/jump-to-source-babel.cjs +1 -1
  105. package/dist-lib/jump-to-source-native.cjs +33 -4
  106. package/dist-lib/kotlin-host.cjs +262 -121
  107. package/dist-lib/menu.cjs +1 -1
  108. package/dist-lib/menu.mjs +1 -1
  109. package/dist-lib/metro-fingerprint-registry.cjs +1 -1
  110. package/dist-lib/metro-fingerprint-registry.mjs +1 -1
  111. package/dist-lib/metro-production-bundle.cjs +101 -7
  112. package/dist-lib/metro-production-bundle.mjs +106 -7
  113. package/dist-lib/metro.cjs +315 -87
  114. package/dist-lib/native-app-identity-project.cjs +101 -26
  115. package/dist-lib/native-app-identity-project.mjs +101 -26
  116. package/dist-lib/native-app-identity.cjs +33 -7
  117. package/dist-lib/native-app-identity.mjs +33 -7
  118. package/dist-lib/native-firebase-metadata.cjs +1 -1
  119. package/dist-lib/native-firebase-metadata.mjs +1 -1
  120. package/dist-lib/plist.cjs +1 -1
  121. package/dist-lib/plist.mjs +1 -1
  122. package/dist-lib/profiles.cjs +1 -1
  123. package/dist-lib/public-brand.cjs +1 -1
  124. package/dist-lib/react-native-host-modules.cjs +1 -1
  125. package/dist-lib/react-native-host-modules.mjs +1 -1
  126. package/dist-lib/scripts/dev-server-scanner.cjs +30 -9
  127. package/dist-lib/sdk.cjs +236 -78
  128. package/dist-lib/sdk.mjs +236 -78
  129. package/dist-lib/skills.cjs +6788 -2636
  130. package/dist-lib/swift-compile.cjs +1637 -299
  131. package/dist-lib/swift-compile.mjs +1558 -219
  132. package/dist-lib/swift-package.cjs +25 -3
  133. package/dist-lib/swift-package.mjs +22 -2
  134. package/dist-lib/swift-submit.cjs +1 -1
  135. package/dist-lib/swift-submit.mjs +1 -1
  136. package/dist-lib/swift-toolchain.cjs +1 -1
  137. package/dist-lib/swift.cjs +1 -1
  138. package/dist-lib/vite-swift.cjs +3675 -1051
  139. package/dist-lib/vite.cjs +142 -20
  140. package/dist-lib/worklet-executable.cjs +1 -1
  141. package/dist-lib/worklet-executable.mjs +1 -1
  142. package/install.sh +1 -1
  143. package/kotlin/AppSources/android/view/Window.kt +4 -1
  144. package/kotlin/AppSources/android/window/BackEvent.kt +24 -0
  145. package/kotlin/AppSources/android/window/OnBackAnimationCallback.kt +7 -0
  146. package/kotlin/AppSources/android/window/OnBackInvokedDispatcher.kt +37 -1
  147. package/kotlin/AppSources/androidx/activity/BackEventCompat.kt +5 -0
  148. package/kotlin/AppSources/androidx/activity/OnBackPressedDispatcher.kt +8 -1
  149. package/kotlin/AppSources/dev/peach/app/PeachApp.kt +14 -0
  150. package/kotlin/CompilerSources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar +1 -0
  151. package/kotlin/CompilerSources/PeachJniIncoming.kt +243 -0
  152. package/kotlin/CompilerSources/PeachJniPlugin.kt +116 -0
  153. package/kotlin/ComposeSources/PeachComposeHost.kt +27 -20
  154. package/kotlin/JniPlatformSources/androidx/annotation/CallSuper.kt +5 -0
  155. package/kotlin/JniPlatformSources/com/facebook/proguard/annotations/DoNotStrip.kt +5 -0
  156. package/kotlin/JniPlatformSources/dalvik/annotation/optimization/FastNative.kt +5 -0
  157. package/kotlin/JniPlatformSources/kotlin/Synchronized.kt +6 -0
  158. package/kotlin/JniPlatformSources/kotlin/jvm/JvmField.kt +5 -0
  159. package/kotlin/JniPlatformSources/kotlin/jvm/JvmStatic.kt +5 -0
  160. package/kotlin/JniSources/com/facebook/jni/HybridData.kt +68 -0
  161. package/kotlin/JniSources/dev/peach/jni/IncomingBindings.kt +157 -0
  162. package/kotlin/JniSources/dev/peach/jni/NativeBindings.kt +79 -0
  163. package/kotlin/JniTransportSources/dev/peach/jni/integration/JniTransport.kt +91 -0
  164. package/kotlin/README.md +18 -0
  165. package/package.json +1 -1
  166. package/scripts/dev-server-scanner.ts +30 -40
  167. package/skills/contrast/SKILL.md +1 -1
  168. package/skills/peach-perf/SKILL.md +25 -0
  169. package/skills/peach-setup/SKILL.md +25 -1
  170. package/src/assetCatalogContract.ts +31 -3
  171. package/src/auth/shared-session.ts +3 -2
  172. package/src/bridge-contract.ts +5 -0
  173. package/src/bundle-channel.mjs +1 -1
  174. package/src/cloud-contract.ts +11 -0
  175. package/src/cloud.ts +2 -2
  176. package/src/config.ts +16 -8
  177. package/src/expo-kotlin-host.ts +7 -2
  178. package/src/guest-bundle.ts +15 -8
  179. package/src/host/agent-host.ts +2 -4
  180. package/src/host/bridge-host.ts +52 -54
  181. package/src/host/fetch-proxy-handler.ts +94 -31
  182. package/src/host/fetch-proxy-overrides.ts +91 -0
  183. package/src/host/replacement-module-handler.ts +11 -10
  184. package/src/host/server-scan-handler.ts +1 -1
  185. package/src/host/staged-host-files.ts +92 -0
  186. package/src/host/websocket-proxy.ts +27 -6
  187. package/src/installed-package-versions.ts +1 -0
  188. package/src/jump-to-source-native.ts +3 -3
  189. package/src/kotlin-host.ts +143 -26
  190. package/src/kotlin-module.ts +52 -0
  191. package/src/metro-layout-parser.ts +78 -0
  192. package/src/metro-production-bundle.ts +59 -10
  193. package/src/native-app-identity-project.ts +67 -3
  194. package/src/native-app-identity.ts +48 -0
  195. package/src/native-firebase-config.ts +3 -2
  196. package/src/native-seam-manifest.ts +0 -44
  197. package/src/nativeAppIconContract.ts +11 -6
  198. package/src/nativeViewSourceContract.ts +49 -0
  199. package/src/nativeViewWebContract.ts +41 -0
  200. package/src/nitro-source.ts +3 -7
  201. package/src/one-widget-metadata.ts +23 -3
  202. package/src/oneWidgetContract.ts +22 -0
  203. package/src/process-group.ts +306 -0
  204. package/src/process-liveness.ts +24 -0
  205. package/src/process-output.ts +21 -2
  206. package/src/serverDiscoveryContract.ts +41 -0
  207. package/src/swift-compile.ts +1063 -265
  208. package/src/swift-design-time.ts +552 -0
  209. package/src/swift-distribution.lock.json +12 -11
  210. package/src/swift-host.ts +133 -25
  211. package/src/swift-objc-sdk.ts +726 -0
  212. package/src/swift-objc-standin.ts +441 -0
  213. package/src/swift-package.ts +25 -0
  214. package/src/swift-packages.ts +25 -10
  215. package/src/swift-standin-config.ts +94 -0
  216. package/src/swift-standin-emit.ts +5 -3
  217. package/src/swift-standins.ts +225 -81
  218. package/src/swiftContract.ts +4 -0
  219. package/src/target-matcher.ts +67 -0
  220. package/src/visit-parsed-program.ts +21 -0
  221. package/src/vite-plugin-one.ts +58 -16
  222. package/src/vite-plugin-swift.ts +1 -0
  223. package/src/vite-plugin.ts +105 -41
  224. package/src/worker-system-chunks.ts +86 -9
  225. package/src/worklets-babel.ts +85 -13
  226. package/src/xcode-project.ts +40 -15
@@ -70,7 +70,6 @@ import type {
70
70
  export const DEFAULT_RNX_CLOUD_ORIGIN = rnxPublicBrand.origin
71
71
  export const MAX_RNX_CLOUD_BUNDLE_BYTES = RNX_CLOUD_MAX_ARTIFACT_BYTES
72
72
 
73
- // every message this module writes names the command through this constant.
74
73
  const REMOTE_COMMAND = 'peach remote'
75
74
  const PREVIEW_COMMAND = 'peach preview'
76
75
 
@@ -170,19 +169,19 @@ function resolveCloudOrigin(value?: string): string {
170
169
  return normalizeCloudOrigin(source)
171
170
  }
172
171
 
173
- function readImmutableBundleSource(bundlePath: string): string {
172
+ function readImmutableBundleSource(bundlePath: string, command: string): string {
174
173
  const filepath = resolve(bundlePath)
175
174
  if (extname(filepath) !== '.js') {
176
- throw new Error(`${REMOTE_COMMAND} requires one local Metro .js bundle`)
175
+ throw new Error(`${command} requires one local Metro .js bundle`)
177
176
  }
178
177
  const descriptor = openSync(filepath, 'r')
179
178
  try {
180
179
  const before = fstatSync(descriptor, { bigint: true })
181
180
  if (!before.isFile()) {
182
- throw new Error(`${REMOTE_COMMAND} bundle must be a regular local file`)
181
+ throw new Error(`${command} bundle must be a regular local file`)
183
182
  }
184
183
  if (before.size <= 0n) {
185
- throw new Error(`${REMOTE_COMMAND} bundle is empty`)
184
+ throw new Error(`${command} bundle is empty`)
186
185
  }
187
186
  const bytes = readFileSync(descriptor)
188
187
  const after = fstatSync(descriptor, { bigint: true })
@@ -193,16 +192,16 @@ function readImmutableBundleSource(bundlePath: string): string {
193
192
  before.mtimeNs !== after.mtimeNs ||
194
193
  bytes.length !== Number(before.size)
195
194
  ) {
196
- throw new Error(`${REMOTE_COMMAND} bundle changed while it was being read`)
195
+ throw new Error(`${command} bundle changed while it was being read`)
197
196
  }
198
197
  let source: string
199
198
  try {
200
199
  source = new TextDecoder('utf-8', { fatal: true }).decode(bytes)
201
200
  } catch {
202
- throw new Error(`${REMOTE_COMMAND} bundle must be valid UTF-8 JavaScript`)
201
+ throw new Error(`${command} bundle must be valid UTF-8 JavaScript`)
203
202
  }
204
203
  if (source.includes('\0') || !source.includes('__d(') || !source.includes('__r(')) {
205
- throw new Error(`${REMOTE_COMMAND} requires a Metro JavaScript bundle`)
204
+ throw new Error(`${command} requires a Metro JavaScript bundle`)
206
205
  }
207
206
  return source
208
207
  } finally {
@@ -210,30 +209,30 @@ function readImmutableBundleSource(bundlePath: string): string {
210
209
  }
211
210
  }
212
211
 
213
- async function fetchBundleSource(bundleUrl: string): Promise<string> {
212
+ async function fetchBundleSource(bundleUrl: string, command: string): Promise<string> {
214
213
  let response: Response
215
214
  try {
216
215
  response = await fetch(bundleUrl)
217
216
  } catch (error) {
218
217
  throw new Error(
219
- `${REMOTE_COMMAND} could not fetch bundle from ${bundleUrl}: ${
218
+ `${command} could not fetch bundle from ${bundleUrl}: ${
220
219
  error instanceof Error ? error.message : String(error)
221
220
  }`,
222
221
  )
223
222
  }
224
223
  if (!response.ok) {
225
224
  throw new Error(
226
- `${REMOTE_COMMAND} could not fetch bundle from ${bundleUrl}: HTTP ${response.status}${
225
+ `${command} could not fetch bundle from ${bundleUrl}: HTTP ${response.status}${
227
226
  response.statusText ? ` ${response.statusText}` : ''
228
227
  }`,
229
228
  )
230
229
  }
231
230
  const text = await response.text()
232
231
  if (text.length === 0) {
233
- throw new Error(`${REMOTE_COMMAND} bundle is empty`)
232
+ throw new Error(`${command} bundle is empty`)
234
233
  }
235
234
  if (text.includes('\0') || !text.includes('__d(') || !text.includes('__r(')) {
236
- throw new Error(`${REMOTE_COMMAND} requires a Metro JavaScript bundle`)
235
+ throw new Error(`${command} requires a Metro JavaScript bundle`)
237
236
  }
238
237
  return text
239
238
  }
@@ -254,20 +253,20 @@ function isRemoteBundleInput(bundleInput: string): boolean {
254
253
  return bundleInput.startsWith('https://') || bundleInput.startsWith('http://')
255
254
  }
256
255
 
257
- async function loadBundleSource(bundleInput: string): Promise<string> {
256
+ async function loadBundleSource(bundleInput: string, command: string): Promise<string> {
258
257
  if (bundleInput.startsWith('http://') && !isLoopbackBundleUrl(bundleInput)) {
259
- throw new Error(`${REMOTE_COMMAND} bundle URL must use HTTPS`)
258
+ throw new Error(`${command} bundle URL must use HTTPS`)
260
259
  }
261
260
  const source = isRemoteBundleInput(bundleInput)
262
- ? await fetchBundleSource(bundleInput)
263
- : readImmutableBundleSource(bundleInput)
261
+ ? await fetchBundleSource(bundleInput, command)
262
+ : readImmutableBundleSource(bundleInput, command)
264
263
  // validate the actual runtime script form without executing guest code or
265
264
  // allocating a host.
266
265
  try {
267
266
  new Script(rewriteGuestImportMeta(source), { filename: bundleInput })
268
267
  } catch (error) {
269
268
  throw new Error(
270
- `${REMOTE_COMMAND} bundle contains invalid JavaScript: ${
269
+ `${command} bundle contains invalid JavaScript: ${
271
270
  error instanceof Error ? error.message : String(error)
272
271
  }`,
273
272
  )
@@ -549,6 +548,7 @@ function unverifiedContentKey(
549
548
  function searchMetroAssetDest(
550
549
  bundlePath: string,
551
550
  descriptors: readonly MetroAssetDescriptor[],
551
+ command: string,
552
552
  assetsDirectory?: string,
553
553
  ): MetroAssetSearch {
554
554
  const bundleDirectory = dirname(resolve(bundlePath))
@@ -616,7 +616,7 @@ function searchMetroAssetDest(
616
616
  // the wrong repair for a dest that only needs rebuilding.
617
617
  if (stale.length > 0) {
618
618
  throw new Error(
619
- `${REMOTE_COMMAND} found this bundle's assets in ${stale.join(', ')}, but their bytes are not the ones it was built against. Rebuild with \`react-native bundle --assets-dest ${stale[0]}\` so the bundle and the images match.`,
619
+ `${command} found this bundle's assets in ${stale.join(', ')}, but their bytes are not the ones it was built against. Rebuild with \`react-native bundle --assets-dest ${stale[0]}\` so the bundle and the images match.`,
620
620
  )
621
621
  }
622
622
  return { dest: null, searched, truncated, unconfirmed: false }
@@ -633,7 +633,7 @@ function searchMetroAssetDest(
633
633
  if (!winner) return { dest: null, searched, truncated, unconfirmed: false }
634
634
  if (distinct.size > 1) {
635
635
  throw new Error(
636
- `${REMOTE_COMMAND} found this bundle's assets in more than one directory and cannot tell which build wrote them: ${rivals
636
+ `${command} found this bundle's assets in more than one directory and cannot tell which build wrote them: ${rivals
637
637
  .map((rival) => rival.dest.directory)
638
638
  .join(
639
639
  ', ',
@@ -649,13 +649,14 @@ function searchMetroAssetDest(
649
649
 
650
650
  async function inferCloudModuleIdentity(
651
651
  source: string,
652
+ command: string,
652
653
  ): Promise<RNXMetroModuleIdentityMetadata> {
653
654
  let receipt
654
655
  try {
655
656
  receipt = await loadMetroFingerprintRegistry()
656
657
  } catch (error) {
657
658
  throw new Error(
658
- `${REMOTE_COMMAND} could not load the Metro fingerprint registry needed to identify this bundle: ${
659
+ `${command} could not load the Metro fingerprint registry needed to identify this bundle: ${
659
660
  error instanceof Error ? error.message : String(error)
660
661
  }. Build it through the published Metro plugin: withPeach(config).`,
661
662
  )
@@ -674,7 +675,7 @@ async function inferCloudModuleIdentity(
674
675
  }
675
676
  } catch (error) {
676
677
  throw new Error(
677
- `${REMOTE_COMMAND} could not infer module identity for this Metro bundle: ${
678
+ `${command} could not infer module identity for this Metro bundle: ${
678
679
  error instanceof Error ? error.message : String(error)
679
680
  }. Build it through the published Metro plugin: withPeach(config).`,
680
681
  )
@@ -775,7 +776,7 @@ async function embedBundleAssets(
775
776
  dest: MetroAssetDest | null
776
777
  substituted: string[]
777
778
  }> {
778
- const source = await loadBundleSource(bundlePath)
779
+ const source = await loadBundleSource(bundlePath, command)
779
780
  const descriptors = extractAssetDescriptors(source).filter(
780
781
  (descriptor) => !isEmbeddedAssetDescriptor(descriptor),
781
782
  )
@@ -791,7 +792,7 @@ async function embedBundleAssets(
791
792
  // an https bundle uses only an explicit local asset destination.
792
793
  const search: MetroAssetSearch =
793
794
  assetsDirectory || !isRemoteBundleInput(bundlePath)
794
- ? searchMetroAssetDest(bundlePath, descriptors, assetsDirectory)
795
+ ? searchMetroAssetDest(bundlePath, descriptors, command, assetsDirectory)
795
796
  : { dest: null, searched: [], truncated: false, unconfirmed: false }
796
797
  const dest = search.dest
797
798
  // `embedMetroAssetFiles` re-parses the bundle, so its descriptors are equal
@@ -877,7 +878,7 @@ async function produceArtifact(
877
878
  assetsDirectory,
878
879
  )
879
880
  const artifact = await buildRnxCloudArtifact(prepared.code, {
880
- inferModuleIdentity: inferCloudModuleIdentity,
881
+ inferModuleIdentity: (source) => inferCloudModuleIdentity(source, command),
881
882
  })
882
883
  const bytes = Buffer.from(artifact.code, 'utf8')
883
884
  const assets = artifact.receipt.assets
@@ -1602,9 +1603,11 @@ export function createCloudBridgeForParsed(
1602
1603
  : new CloudBridge(session)
1603
1604
  }
1604
1605
 
1605
- // stops the box, which ends the simulator it holds. an account api key stops
1606
- // with its own authority; a session login stops with the login, falling back
1607
- // to the box token the session already holds.
1606
+ // deletes the box, which ends the simulator it holds. a cloud session box is
1607
+ // the prebuilt bundle `peach ios --remote` created, so it holds nothing but that
1608
+ // bundle: stopping it would only keep its name taken and its storage held. an
1609
+ // account api key deletes with its own authority; a session login deletes with
1610
+ // the login, falling back to the box token the session already holds.
1608
1611
  export async function closeCloudBox(session: CloudSession): Promise<void> {
1609
1612
  if (session.service !== 'box') {
1610
1613
  if (!session.boxId || !session.boxToken) return
@@ -1631,7 +1634,7 @@ export async function closeCloudBox(session: CloudSession): Promise<void> {
1631
1634
  let response: Response
1632
1635
  try {
1633
1636
  response = await fetch(
1634
- `${session.apiOrigin}/v1/boxes/${encodeURIComponent(session.boxId)}/stop`,
1637
+ `${session.apiOrigin}/v1/boxes/${encodeURIComponent(session.boxId)}/delete`,
1635
1638
  {
1636
1639
  method: 'POST',
1637
1640
  headers: { authorization: authHeader },
@@ -1650,6 +1653,15 @@ export async function closeCloudBox(session: CloudSession): Promise<void> {
1650
1653
  }
1651
1654
  }
1652
1655
 
1656
+ // the box's page reported that the app threw while booting, so no simulator is
1657
+ // coming and the box should end rather than bill.
1658
+ export class BoxBootFailedError extends Error {
1659
+ constructor(boxId: string, detail: string) {
1660
+ super(`box ${boxId} could not boot the app: ${detail}`)
1661
+ this.name = 'BoxBootFailedError'
1662
+ }
1663
+ }
1664
+
1653
1665
  export interface BoxDisplayGrant {
1654
1666
  url: string
1655
1667
  authToken: string
@@ -1710,6 +1722,17 @@ export async function requestBoxWatchUrl(options: {
1710
1722
  }`,
1711
1723
  )
1712
1724
  }
1725
+ if (response.status === 422) {
1726
+ const text = await response.text()
1727
+ let failure: unknown = null
1728
+ try {
1729
+ failure = JSON.parse(text)
1730
+ } catch {}
1731
+ if (isRecord(failure) && failure.code === 'boot-failed') {
1732
+ throw new BoxBootFailedError(options.boxId, String(failure.error))
1733
+ }
1734
+ throw responseError(response.status, text)
1735
+ }
1713
1736
  if (response.status === 409) {
1714
1737
  // the page has not attached its simulator yet. this is the normal state
1715
1738
  // right after a create, so wait rather than refusing.
@@ -0,0 +1,39 @@
1
+ // peach mode agent-indicator — show or hide the connected agent indicator and
2
+ // its resting agent cursor over the device in the running peach sim, so
3
+ // scripted recordings can film clean devices while the CLI drives them.
4
+
5
+ import { peachExit } from '../run-rnx'
6
+ import { runShellBooleanMode, type ShellBooleanModeOptions } from './shell-boolean-mode'
7
+
8
+ export async function runAgentIndicatorMode(
9
+ args: string[],
10
+ opts: ShellBooleanModeOptions,
11
+ ) {
12
+ if (args.includes('--help') || args.includes('-h')) {
13
+ console.log(`
14
+ peach mode agent-indicator — toggle the connected agent indicator
15
+
16
+ usage:
17
+ peach mode agent-indicator [on|off|toggle]
18
+
19
+ arguments:
20
+ on show the indicator while a CLI or agent drives the sim (default)
21
+ off hide it and the agent cursor that rests under it, for clean recordings
22
+ of scripted sims
23
+ toggle flip the current state (default action)
24
+
25
+ the setting persists. a shell url can also seed it with agentIndicator=0.
26
+
27
+ examples:
28
+ peach mode agent-indicator off
29
+ peach --sim a1 mode agent-indicator on
30
+ `)
31
+ peachExit(0)
32
+ }
33
+
34
+ await runShellBooleanMode(args, opts, {
35
+ modeKey: 'agentIndicator',
36
+ displayName: 'agent-indicator',
37
+ actionId: 'toggle-agent-indicator',
38
+ })
39
+ }
@@ -124,7 +124,7 @@ function apiOrigin(args: string[], auth: CliAuth): string {
124
124
  return (
125
125
  flag(args, 'api-origin') ??
126
126
  process.env[API_ORIGIN_ENV]?.trim() ??
127
- (auth.kind === 'session' ? auth.origin : 'https://contrast.dev')
127
+ (auth.kind === 'session' ? auth.origin : rnxPublicBrand.origin)
128
128
  ).replace(/\/+$/, '')
129
129
  }
130
130
 
@@ -186,6 +186,7 @@ async function runCloudBoxUse(args: string[]): Promise<number> {
186
186
  apiOrigin: apiOrigin(args, auth),
187
187
  apiKey,
188
188
  boxId: named,
189
+ accountId: cloudAccountIdOrExit(auth),
189
190
  })
190
191
  // attach for the Box's own name: the caller may have typed either an id or
191
192
  // a name, and the default has to be able to print the one a person reads.
@@ -361,7 +362,12 @@ async function runCloudBoxConnect(args: string[]): Promise<number> {
361
362
 
362
363
  let session: BoxShellSession | null = null
363
364
  try {
364
- const reference = await resolveCloudBoxReference({ apiOrigin: origin, apiKey, boxId })
365
+ const reference = await resolveCloudBoxReference({
366
+ apiOrigin: origin,
367
+ apiKey,
368
+ boxId,
369
+ accountId: cloudAccountIdOrExit(auth),
370
+ })
365
371
  const box = await createBoxClient(
366
372
  createCloudBoxProvider({
367
373
  endpoint: reference.endpoint,
@@ -406,7 +412,7 @@ async function runCloudBoxConnect(args: string[]): Promise<number> {
406
412
  * person sees is what the agent is working on, and two people opening the same
407
413
  * box see the same screen.
408
414
  *
409
- * the url carries no credential: it is the box's page on the Contrast origin,
415
+ * the url carries no credential: it is the box's page on its account origin,
410
416
  * and opening it signed in resolves the box through the account service. that
411
417
  * makes it a link somebody can keep, paste, or reload.
412
418
  */
@@ -428,6 +434,7 @@ async function runCloudBoxOpen(args: string[]): Promise<number> {
428
434
  apiOrigin: apiOrigin(args, auth),
429
435
  apiKey,
430
436
  boxId,
437
+ accountId: cloudAccountIdOrExit(auth),
431
438
  })
432
439
  const connectUrl = cloudBoxConnectUrl(reference.endpoint, reference.boxId)
433
440
  // ensure first: it is what navigates the generation's tab to the Box page,
@@ -491,6 +498,7 @@ export async function openInBrowser(url: string): Promise<number> {
491
498
 
492
499
  function cloudBoxClient(options: {
493
500
  endpoint: string
501
+ fresh?: boolean
494
502
  apiOrigin: string
495
503
  apiKey: string
496
504
  accountId: string | null
@@ -498,6 +506,7 @@ function cloudBoxClient(options: {
498
506
  return createBoxClient(
499
507
  createCloudBoxProvider({
500
508
  endpoint: options.endpoint,
509
+ fresh: options.fresh,
501
510
  authority: {
502
511
  kind: 'account',
503
512
  apiOrigin: options.apiOrigin,
@@ -517,6 +526,7 @@ function cloudBoxClient(options: {
517
526
  * that bundle instead of building anything.
518
527
  */
519
528
  export async function createPrebuiltBundleBox(options: {
529
+ fresh?: boolean
520
530
  bundle: Uint8Array
521
531
  nativeProjectDir?: string
522
532
  // the simulator's device model, or null for the Box page's default