peachsim 0.1.527

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (324) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +640 -0
  3. package/cli/app-config.ts +82 -0
  4. package/cli/app-fonts.ts +388 -0
  5. package/cli/app-project.ts +59 -0
  6. package/cli/app-splash.ts +171 -0
  7. package/cli/app-state-reset.ts +26 -0
  8. package/cli/auth.ts +184 -0
  9. package/cli/bin.ts +59 -0
  10. package/cli/bridge-diagnostics.ts +226 -0
  11. package/cli/bridge-flow-runner.ts +2806 -0
  12. package/cli/browser-backend.ts +44 -0
  13. package/cli/browser-evals.ts +96 -0
  14. package/cli/cli-update.ts +261 -0
  15. package/cli/cloud-client.ts +1530 -0
  16. package/cli/cloud-dispatch.ts +206 -0
  17. package/cli/cloud-session.ts +227 -0
  18. package/cli/command-contract.ts +221 -0
  19. package/cli/command-registry.ts +304 -0
  20. package/cli/commands/agent-wrapper.ts +1048 -0
  21. package/cli/commands/agent.ts +439 -0
  22. package/cli/commands/app-fonts.ts +99 -0
  23. package/cli/commands/assert.ts +542 -0
  24. package/cli/commands/auth.ts +71 -0
  25. package/cli/commands/box/bundle-plane.ts +71 -0
  26. package/cli/commands/box/checkout-plane.ts +256 -0
  27. package/cli/commands/box/plane-share.ts +208 -0
  28. package/cli/commands/box/rnx-command.ts +60 -0
  29. package/cli/commands/box/search-command.ts +157 -0
  30. package/cli/commands/box.ts +925 -0
  31. package/cli/commands/camera.ts +266 -0
  32. package/cli/commands/cleanup.ts +169 -0
  33. package/cli/commands/compat.ts +176 -0
  34. package/cli/commands/config.ts +34 -0
  35. package/cli/commands/control.ts +2154 -0
  36. package/cli/commands/cpu-profile.ts +941 -0
  37. package/cli/commands/daemon-mac-app.ts +165 -0
  38. package/cli/commands/daemon.ts +800 -0
  39. package/cli/commands/debug.ts +751 -0
  40. package/cli/commands/desktop.ts +39 -0
  41. package/cli/commands/detect.ts +201 -0
  42. package/cli/commands/detox.ts +481 -0
  43. package/cli/commands/device.ts +134 -0
  44. package/cli/commands/diagnose.ts +589 -0
  45. package/cli/commands/do-chain.ts +408 -0
  46. package/cli/commands/electron.ts +94 -0
  47. package/cli/commands/film.ts +379 -0
  48. package/cli/commands/flow.ts +1240 -0
  49. package/cli/commands/inspect/actions.ts +516 -0
  50. package/cli/commands/inspect/core.ts +2086 -0
  51. package/cli/commands/inspect/count.ts +17 -0
  52. package/cli/commands/inspect/describe.ts +119 -0
  53. package/cli/commands/inspect/env.ts +25 -0
  54. package/cli/commands/inspect/find.ts +169 -0
  55. package/cli/commands/inspect/get-layout.ts +53 -0
  56. package/cli/commands/inspect/keyboard.ts +54 -0
  57. package/cli/commands/inspect/list.ts +175 -0
  58. package/cli/commands/inspect/memory.ts +215 -0
  59. package/cli/commands/inspect/redaction.ts +39 -0
  60. package/cli/commands/inspect/resolve-target.ts +77 -0
  61. package/cli/commands/inspect/screens.ts +78 -0
  62. package/cli/commands/inspect/settle.ts +32 -0
  63. package/cli/commands/inspect/settling.ts +197 -0
  64. package/cli/commands/inspect/shared.ts +259 -0
  65. package/cli/commands/inspect/sleep.ts +15 -0
  66. package/cli/commands/inspect/tree.ts +23 -0
  67. package/cli/commands/inspect/url.ts +17 -0
  68. package/cli/commands/inspect/wait-event.ts +211 -0
  69. package/cli/commands/inspect/wait-idle.ts +25 -0
  70. package/cli/commands/inspect/wait-ready.ts +87 -0
  71. package/cli/commands/inspect/wait-selector.ts +59 -0
  72. package/cli/commands/inspect/wait.ts +32 -0
  73. package/cli/commands/inspect.ts +4738 -0
  74. package/cli/commands/install-cli.ts +275 -0
  75. package/cli/commands/install-desktop.ts +385 -0
  76. package/cli/commands/login.ts +357 -0
  77. package/cli/commands/logout.ts +24 -0
  78. package/cli/commands/maestro-generate.ts +375 -0
  79. package/cli/commands/maestro.ts +536 -0
  80. package/cli/commands/mode.ts +58 -0
  81. package/cli/commands/no-bridge-hint.ts +80 -0
  82. package/cli/commands/perf.ts +67 -0
  83. package/cli/commands/permissions.ts +203 -0
  84. package/cli/commands/platform.ts +410 -0
  85. package/cli/commands/preview.ts +103 -0
  86. package/cli/commands/profile.ts +106 -0
  87. package/cli/commands/react.ts +353 -0
  88. package/cli/commands/record.ts +1435 -0
  89. package/cli/commands/report-issue.ts +305 -0
  90. package/cli/commands/reset.ts +89 -0
  91. package/cli/commands/runtime.ts +348 -0
  92. package/cli/commands/screenshot-command.ts +106 -0
  93. package/cli/commands/screenshot-layers.ts +144 -0
  94. package/cli/commands/screenshot-mode.ts +38 -0
  95. package/cli/commands/screenshot.ts +492 -0
  96. package/cli/commands/screenshots-capture.ts +608 -0
  97. package/cli/commands/screenshots.ts +127 -0
  98. package/cli/commands/serve.ts +139 -0
  99. package/cli/commands/setup.ts +416 -0
  100. package/cli/commands/shell-boolean-mode.ts +82 -0
  101. package/cli/commands/skills.ts +468 -0
  102. package/cli/commands/slides.ts +357 -0
  103. package/cli/commands/state.ts +94 -0
  104. package/cli/commands/storage.ts +58 -0
  105. package/cli/commands/telemetry.ts +55 -0
  106. package/cli/commands/test.ts +535 -0
  107. package/cli/commands/three-mode.ts +771 -0
  108. package/cli/commands/timeline.ts +121 -0
  109. package/cli/commands/upgrade.ts +167 -0
  110. package/cli/commands/upload.ts +1203 -0
  111. package/cli/commands/version.ts +63 -0
  112. package/cli/commands/welcome.ts +17 -0
  113. package/cli/commands/what-happened.ts +328 -0
  114. package/cli/current-sim.ts +217 -0
  115. package/cli/desktop-companion.ts +315 -0
  116. package/cli/drivers/electron.ts +70 -0
  117. package/cli/drivers/index.ts +20 -0
  118. package/cli/drivers/playwright-provisioning.ts +184 -0
  119. package/cli/drivers/playwright-sim-host.ts +537 -0
  120. package/cli/drivers/playwright.ts +209 -0
  121. package/cli/drivers/registry.ts +65 -0
  122. package/cli/drivers/types.ts +102 -0
  123. package/cli/flow-export.ts +55 -0
  124. package/cli/flow-file.ts +142 -0
  125. package/cli/flow-live-status.ts +126 -0
  126. package/cli/flow-session.ts +140 -0
  127. package/cli/help-core.ts +143 -0
  128. package/cli/help.ts +44 -0
  129. package/cli/hidden-runtime-alias.ts +19 -0
  130. package/cli/hints.ts +214 -0
  131. package/cli/inspect-notice-state.ts +114 -0
  132. package/cli/internal-child.ts +176 -0
  133. package/cli/maestro-js.ts +323 -0
  134. package/cli/main.ts +704 -0
  135. package/cli/open-url.ts +8 -0
  136. package/cli/outbound-endpoints.ts +830 -0
  137. package/cli/parent-pid.ts +204 -0
  138. package/cli/parse-args.ts +189 -0
  139. package/cli/privacy.ts +53 -0
  140. package/cli/prompt.ts +56 -0
  141. package/cli/recording-access.ts +108 -0
  142. package/cli/registry.ts +2 -0
  143. package/cli/resolve-assets.ts +63 -0
  144. package/cli/rnx-in-process.ts +98 -0
  145. package/cli/run-registry.ts +226 -0
  146. package/cli/run-rnx-core.ts +148 -0
  147. package/cli/run-rnx.ts +137 -0
  148. package/cli/runtime-notes.ts +66 -0
  149. package/cli/runtime-summary.ts +25 -0
  150. package/cli/secure-file.ts +31 -0
  151. package/cli/self-invocation.ts +58 -0
  152. package/cli/send-to-box.ts +109 -0
  153. package/cli/setup-repository.ts +191 -0
  154. package/cli/shell-init.ts +98 -0
  155. package/cli/standalone.ts +4 -0
  156. package/cli/telemetry.ts +84 -0
  157. package/cli/ui.ts +68 -0
  158. package/cli/ws-bridge.ts +1020 -0
  159. package/detox/attempt-events.cjs +23 -0
  160. package/detox/colors.ts +54 -0
  161. package/detox/config-loader.ts +135 -0
  162. package/detox/element-types.ts +48 -0
  163. package/detox/expectations.ts +534 -0
  164. package/detox/gestures.ts +501 -0
  165. package/detox/index.ts +2694 -0
  166. package/detox/jest-preset.cjs +29 -0
  167. package/detox/jest-setup-after-env.cjs +22 -0
  168. package/detox/matchers.ts +30 -0
  169. package/detox/motion-change.cjs +109 -0
  170. package/detox/motion-change.d.cts +35 -0
  171. package/detox/navigation-lifecycle.ts +290 -0
  172. package/detox/navigation.ts +43 -0
  173. package/detox/proof-frame.cjs +114 -0
  174. package/detox/record-block-execution.cjs +78 -0
  175. package/detox/screenshots/animated-color-test-rest-norngh.png +0 -0
  176. package/detox/screenshots/color-test-after-drag-norngh.png +0 -0
  177. package/detox/screenshots/color-test-rest-norngh.png +0 -0
  178. package/detox/screenshots/theme-blue-toggle.png +0 -0
  179. package/detox/screenshots/theme-blue.png +0 -0
  180. package/detox/screenshots/theme-red-toggle.png +0 -0
  181. package/detox/screenshots/theme-red.png +0 -0
  182. package/dist-lib/agent-daemon-client.cjs +502 -0
  183. package/dist-lib/agent-events.cjs +48 -0
  184. package/dist-lib/agent-identity.cjs +53 -0
  185. package/dist-lib/agent-sessions.cjs +759 -0
  186. package/dist-lib/attached-projects.cjs +526 -0
  187. package/dist-lib/auth/shared-session.cjs +208 -0
  188. package/dist-lib/backend-origin.cjs +88 -0
  189. package/dist-lib/beta.cjs +41 -0
  190. package/dist-lib/beta.mjs +13 -0
  191. package/dist-lib/bridge-constants.cjs +161 -0
  192. package/dist-lib/bridge-contract-input.cjs +451 -0
  193. package/dist-lib/bridge-contract-input.mjs +414 -0
  194. package/dist-lib/bridge-contract.cjs +130 -0
  195. package/dist-lib/bridge-contract.mjs +94 -0
  196. package/dist-lib/capture-contract.cjs +220 -0
  197. package/dist-lib/capture-contract.mjs +190 -0
  198. package/dist-lib/cli-constants.cjs +32 -0
  199. package/dist-lib/cloud-contract.cjs +194 -0
  200. package/dist-lib/cloud-contract.mjs +145 -0
  201. package/dist-lib/cloud.cjs +670 -0
  202. package/dist-lib/cloud.mjs +639 -0
  203. package/dist-lib/config.cjs +96 -0
  204. package/dist-lib/detox/index.cjs +3789 -0
  205. package/dist-lib/detox/jest-preset.cjs +29 -0
  206. package/dist-lib/dev-bundle-resolution.cjs +290 -0
  207. package/dist-lib/home-paths.cjs +638 -0
  208. package/dist-lib/host/bridge-host.cjs +10903 -0
  209. package/dist-lib/host/fetch-proxy-handler.cjs +421 -0
  210. package/dist-lib/host/fetch-proxy-overrides.cjs +63 -0
  211. package/dist-lib/host/fetch-proxy-overrides.mjs +37 -0
  212. package/dist-lib/host/replacement-module-handler.cjs +469 -0
  213. package/dist-lib/host/websocket-proxy.cjs +298 -0
  214. package/dist-lib/index.cjs +4268 -0
  215. package/dist-lib/jump-to-source-babel.cjs +93 -0
  216. package/dist-lib/jump-to-source-native.cjs +112 -0
  217. package/dist-lib/menu.cjs +646 -0
  218. package/dist-lib/menu.mjs +604 -0
  219. package/dist-lib/metro-fingerprint-registry.cjs +186 -0
  220. package/dist-lib/metro-fingerprint-registry.mjs +156 -0
  221. package/dist-lib/metro-production-bundle.cjs +329 -0
  222. package/dist-lib/metro-production-bundle.mjs +295 -0
  223. package/dist-lib/metro.cjs +904 -0
  224. package/dist-lib/profiles.cjs +246 -0
  225. package/dist-lib/public-brand.cjs +39 -0
  226. package/dist-lib/react-native-host-modules.cjs +51 -0
  227. package/dist-lib/react-native-host-modules.mjs +25 -0
  228. package/dist-lib/render-mode.cjs +54 -0
  229. package/dist-lib/scripts/dev-server-scanner.cjs +1232 -0
  230. package/dist-lib/sdk.cjs +2433 -0
  231. package/dist-lib/sdk.mjs +2336 -0
  232. package/dist-lib/skills.cjs +39132 -0
  233. package/dist-lib/swift-submit.cjs +92 -0
  234. package/dist-lib/swift-submit.mjs +65 -0
  235. package/dist-lib/swift.cjs +1646 -0
  236. package/dist-lib/vite.cjs +1591 -0
  237. package/package.json +290 -0
  238. package/scripts/dev-server-scanner.ts +1291 -0
  239. package/scripts/optional-demo-registry.ts +113 -0
  240. package/skills/contrast/SKILL.md +165 -0
  241. package/skills/rnx-debug/SKILL.md +433 -0
  242. package/skills/rnx-perf/SKILL.md +399 -0
  243. package/skills/rnx-setup/SKILL.md +172 -0
  244. package/skills/rnx-test/SKILL.md +321 -0
  245. package/skills/rnx-visual/SKILL.md +126 -0
  246. package/src/agent-daemon-client.ts +410 -0
  247. package/src/agent-events.ts +71 -0
  248. package/src/agent-identity.ts +23 -0
  249. package/src/agent-prompt.ts +71 -0
  250. package/src/agent-sessions.ts +580 -0
  251. package/src/app-url.ts +39 -0
  252. package/src/attached-projects.ts +539 -0
  253. package/src/auth/shared-session.ts +235 -0
  254. package/src/backend-origin.ts +70 -0
  255. package/src/beta.ts +19 -0
  256. package/src/bridge-constants.ts +175 -0
  257. package/src/bridge-contract-input.ts +502 -0
  258. package/src/bridge-contract.ts +558 -0
  259. package/src/browser-cache.ts +16 -0
  260. package/src/capture-contract.ts +587 -0
  261. package/src/cli-constants.ts +1 -0
  262. package/src/cli-version.ts +40 -0
  263. package/src/cloud-contract.ts +274 -0
  264. package/src/cloud.ts +762 -0
  265. package/src/codex-client.ts +234 -0
  266. package/src/config.ts +132 -0
  267. package/src/connect.ts +101 -0
  268. package/src/dev-bundle-resolution.ts +346 -0
  269. package/src/dev-server-open.ts +198 -0
  270. package/src/disk-cleanup.ts +540 -0
  271. package/src/flow-timeout-scale.ts +15 -0
  272. package/src/home-paths.ts +806 -0
  273. package/src/host/agent-host.ts +666 -0
  274. package/src/host/bridge-host.ts +2788 -0
  275. package/src/host/fetch-proxy-handler.ts +449 -0
  276. package/src/host/fetch-proxy-overrides.ts +51 -0
  277. package/src/host/open-url.ts +89 -0
  278. package/src/host/plane-host.ts +203 -0
  279. package/src/host/replacement-module-handler.ts +158 -0
  280. package/src/host/websocket-proxy.ts +315 -0
  281. package/src/index.ts +71 -0
  282. package/src/jump-to-source-babel.ts +135 -0
  283. package/src/jump-to-source-native.ts +102 -0
  284. package/src/menu.ts +777 -0
  285. package/src/metro-fingerprint-registry.ts +302 -0
  286. package/src/metro-plugin.ts +341 -0
  287. package/src/metro-production-bundle.ts +471 -0
  288. package/src/native-dev-bundle-url.ts +118 -0
  289. package/src/native-seam-manifest.ts +341 -0
  290. package/src/playwright-host-log.ts +16 -0
  291. package/src/plist.ts +27 -0
  292. package/src/poll-until.ts +123 -0
  293. package/src/profiles.ts +179 -0
  294. package/src/public-brand.ts +9 -0
  295. package/src/react-native-host-modules.ts +26 -0
  296. package/src/render-mode.ts +27 -0
  297. package/src/rolldown-plugin-swift.ts +240 -0
  298. package/src/runtime-assets.ts +233 -0
  299. package/src/runtime-delivery.ts +54 -0
  300. package/src/screenshots/compose.ts +422 -0
  301. package/src/screenshots/frame-compose.ts +514 -0
  302. package/src/screenshots/orchestrate.ts +256 -0
  303. package/src/screenshots/org-deck.ts +91 -0
  304. package/src/screenshots/registry.ts +58 -0
  305. package/src/screenshots/schema.ts +364 -0
  306. package/src/sdk.ts +15 -0
  307. package/src/settings-bundle.ts +418 -0
  308. package/src/shell-bridge-identity.ts +15 -0
  309. package/src/sim-client.ts +697 -0
  310. package/src/skills/builtin/a11y-review.ts +132 -0
  311. package/src/skills/builtin/compat-check.ts +71 -0
  312. package/src/skills/builtin/maestro-test.ts +121 -0
  313. package/src/skills/builtin/perf-profile.ts +90 -0
  314. package/src/skills/builtin/screenshot-all.ts +52 -0
  315. package/src/skills/builtin/visual-diff.ts +100 -0
  316. package/src/skills/registry.ts +107 -0
  317. package/src/skills/types.ts +41 -0
  318. package/src/swift-submit.ts +185 -0
  319. package/src/swift.ts +7 -0
  320. package/src/team-machine-prompt.ts +73 -0
  321. package/src/vite-plugin-one.ts +264 -0
  322. package/src/vite-plugin-swift.ts +620 -0
  323. package/src/vite-plugin.ts +1946 -0
  324. package/src/worklets-babel.ts +250 -0
@@ -0,0 +1,31 @@
1
+ import { randomUUID } from 'node:crypto'
2
+ import {
3
+ closeSync,
4
+ fchmodSync,
5
+ fsyncSync,
6
+ mkdirSync,
7
+ openSync,
8
+ renameSync,
9
+ rmSync,
10
+ writeFileSync,
11
+ } from 'node:fs'
12
+ import { dirname } from 'node:path'
13
+
14
+ export function writeSecureJsonAtomic(filepath: string, value: unknown): void {
15
+ mkdirSync(dirname(filepath), { recursive: true })
16
+ const temporary = `${filepath}.tmp-${process.pid}-${randomUUID()}`
17
+ let descriptor: number | null = null
18
+ try {
19
+ descriptor = openSync(temporary, 'wx', 0o600)
20
+ fchmodSync(descriptor, 0o600)
21
+ writeFileSync(descriptor, `${JSON.stringify(value, null, 2)}\n`)
22
+ fsyncSync(descriptor)
23
+ closeSync(descriptor)
24
+ descriptor = null
25
+ renameSync(temporary, filepath)
26
+ } catch (error) {
27
+ if (descriptor !== null) closeSync(descriptor)
28
+ rmSync(temporary, { force: true })
29
+ throw error
30
+ }
31
+ }
@@ -0,0 +1,58 @@
1
+ // how this CLI re-invokes itself as a child process.
2
+ //
3
+ // two shapes exist and only the build decides which one is running:
4
+ //
5
+ // standalone the bun-compiled executable IS the entry point. it takes the
6
+ // CLI argv directly and there is no script to hand it.
7
+ // entry script every other shape (dev checkout, the repository dist-cli
8
+ // bundle) runs through the javascript runtime executing now,
9
+ // with the script as its first argument.
10
+ //
11
+ // a presence check cannot tell them apart. inside the compiled binary
12
+ // `process.argv[1]` is a path in bun's virtual filesystem (`/$bunfs/root/...`)
13
+ // that passes `existsSync` in-process, so every site that probed argv[1]
14
+ // re-invoked itself as `rnx <bunfs path> <command>` and the child died on
15
+ // `unknown command`. the build-time constant is the only honest signal.
16
+
17
+ import { existsSync } from 'node:fs'
18
+ import { basename, dirname, join } from 'node:path'
19
+ import { fileURLToPath } from 'node:url'
20
+ import { rnxPublicBrand } from '../src/public-brand.ts'
21
+ import { IS_STANDALONE } from './standalone.ts'
22
+
23
+ export interface RnxSelfInvocation {
24
+ /** absolute path to spawn. */
25
+ executable: string
26
+ /** arguments that precede the CLI's own argv. */
27
+ prefixArgs: string[]
28
+ }
29
+
30
+ function sourceEntryPath(): string | null {
31
+ try {
32
+ const candidate = join(dirname(fileURLToPath(import.meta.url)), 'bin.ts')
33
+ return existsSync(candidate) ? candidate : null
34
+ } catch {
35
+ return null
36
+ }
37
+ }
38
+
39
+ function isCliEntry(path: string): boolean {
40
+ const name = basename(path)
41
+ return name === 'bin.ts' || name === 'bin.js'
42
+ }
43
+
44
+ export function rnxSelfInvocation(): RnxSelfInvocation {
45
+ if (IS_STANDALONE) return { executable: process.execPath, prefixArgs: [] }
46
+ const entry = process.argv[1]
47
+ if (entry && isCliEntry(entry)) {
48
+ return { executable: process.execPath, prefixArgs: [entry] }
49
+ }
50
+ const sourceEntry = sourceEntryPath()
51
+ if (sourceEntry) {
52
+ return { executable: process.execPath, prefixArgs: [sourceEntry] }
53
+ }
54
+ if (!entry) {
55
+ throw new Error(`could not locate the ${rnxPublicBrand.commandName} entry script`)
56
+ }
57
+ return { executable: process.execPath, prefixArgs: [entry] }
58
+ }
@@ -0,0 +1,109 @@
1
+ // "send to box": the running local app becomes a nano cloud simulator.
2
+ //
3
+ // one function serves both callers. the rail button in the shell reaches it
4
+ // through the local daemon's `/__send-to-box` route, and `rnx box send` calls
5
+ // it directly; both hand it a way to run a bridge command against the sim that
6
+ // is running the app, and both get the same simulator id and URL back.
7
+
8
+ import { isStorageSnapshot } from '../../sootsim-engine/src/preview/storage-snapshot-contract.ts'
9
+ import {
10
+ DEFAULT_DEVICE_BY_PLATFORM,
11
+ getRuntimePlatform,
12
+ listDeviceModels,
13
+ } from '../../sootsim-engine/src/settings/devices.ts'
14
+ import { toRNXProductionBundleUrl } from '../src/metro-production-bundle.ts'
15
+ import { createCloudBox } from './cloud-client.ts'
16
+ import type { StorageSnapshot } from '../../sootsim-engine/src/preview/storage-snapshot-contract.ts'
17
+ import type { DeviceModel } from '../../sootsim-engine/src/settings/devices.ts'
18
+ import type { BridgeCommandType } from '../src/bridge-contract.ts'
19
+
20
+ export interface SendToBoxOptions {
21
+ /** runs one bridge command against the sim running the app. */
22
+ send: (command: { type: BridgeCommandType; [key: string]: unknown }) => Promise<unknown>
23
+ authorization: string
24
+ /** the account the box bills. an api key names its own, so it sends none. */
25
+ accountId: string | null
26
+ /** the cloud device model. defaults to the device the sim is running on. */
27
+ device?: DeviceModel
28
+ /**
29
+ * carry the signed-in session into the box. on by default in the shell: the
30
+ * box is the sender's own, and an app that boots signed out is not the app
31
+ * they were looking at.
32
+ */
33
+ carryAuth: boolean
34
+ boxName?: string | null
35
+ apiOrigin?: string
36
+ simId?: string
37
+ }
38
+
39
+ export interface SendToBoxResult {
40
+ boxId: string
41
+ simId: string
42
+ url: string
43
+ carriedAuth: boolean
44
+ warnings: string[]
45
+ }
46
+
47
+ function isRecord(value: unknown): value is Record<string, unknown> {
48
+ return !!value && typeof value === 'object' && !Array.isArray(value)
49
+ }
50
+
51
+ // the box opens on the device the sender was looking at. a remote simulator is
52
+ // iOS only, so an Android sim sends to the default iPhone rather than refusing.
53
+ function cloudDevice(capturedModel: unknown): DeviceModel {
54
+ const captured = listDeviceModels().find((model) => model === capturedModel)
55
+ return captured && getRuntimePlatform(captured) === 'ios'
56
+ ? captured
57
+ : DEFAULT_DEVICE_BY_PLATFORM.ios
58
+ }
59
+
60
+ export async function sendToBox(options: SendToBoxOptions): Promise<SendToBoxResult> {
61
+ const captured: unknown = await options.send({
62
+ type: 'evaluate',
63
+ simId: options.simId,
64
+ code: '(typeof window.__sootsimCaptureBundle === "function") ? window.__sootsimCaptureBundle() : null',
65
+ })
66
+ const bundleUrl = isRecord(captured) ? captured.bundleUrl : null
67
+ if (!isRecord(captured) || typeof bundleUrl !== 'string' || !bundleUrl) {
68
+ throw new Error(
69
+ 'send to box needs a simulator running an app: this one has no bundle loaded',
70
+ )
71
+ }
72
+ // the snapshot is taken before the production bundle is fetched, so the box
73
+ // gets the state the app was in when the button was pressed rather than the
74
+ // state it drifted to while Metro rebuilt.
75
+ const dumped: unknown = await options.send({
76
+ type: 'storageSnapshot',
77
+ simId: options.simId,
78
+ storageSnapshotOptions: { includeCredentials: options.carryAuth },
79
+ })
80
+ if (!isStorageSnapshot(dumped)) {
81
+ throw new Error('the simulator returned a storage snapshot this build cannot read')
82
+ }
83
+ const storage: StorageSnapshot = dumped
84
+ const created = await createCloudBox({
85
+ bundlePath: toRNXProductionBundleUrl(bundleUrl),
86
+ device:
87
+ options.device ??
88
+ cloudDevice(isRecord(captured.deviceSpec) ? captured.deviceSpec.model : null),
89
+ authorization: options.authorization,
90
+ accountId: options.accountId,
91
+ apiOrigin: options.apiOrigin,
92
+ boxName: options.boxName ?? null,
93
+ storage,
94
+ })
95
+ const simId = created.receipt.simulator.simId
96
+ const url = `${created.session.apiOrigin.replace(/\/$/, '')}/sim/${simId}`
97
+ // the local terminal is where an agent driving this checkout reads results,
98
+ // so the id and the URL land there whichever caller asked for the send.
99
+ console.log(` box simulator: ${simId}`)
100
+ console.log(` ${url}`)
101
+ for (const warning of created.warnings) console.warn(` warning: ${warning}`)
102
+ return {
103
+ boxId: created.receipt.boxId,
104
+ simId,
105
+ url,
106
+ carriedAuth: options.carryAuth,
107
+ warnings: created.warnings,
108
+ }
109
+ }
@@ -0,0 +1,191 @@
1
+ import { spawn } from 'node:child_process'
2
+ import fs from 'node:fs'
3
+ import path from 'node:path'
4
+ import { rnxPublicBrand } from '../src/public-brand'
5
+
6
+ export type SetupPackageManager = 'bun' | 'pnpm' | 'npm'
7
+
8
+ export interface RepositoryOperation {
9
+ command: string
10
+ args: string[]
11
+ description: string
12
+ }
13
+
14
+ export interface RepositorySetupPlan {
15
+ appDir: string
16
+ packageManager: SetupPackageManager
17
+ operations: RepositoryOperation[]
18
+ }
19
+
20
+ interface PackageManifest {
21
+ scripts?: Record<string, string>
22
+ devDependencies?: Record<string, string>
23
+ }
24
+
25
+ export function findRepositoryPackageRoot(startDir: string): string | null {
26
+ let current = path.resolve(startDir)
27
+ let packageRoot: string | null = null
28
+ while (true) {
29
+ if (!packageRoot && fs.existsSync(path.join(current, 'package.json'))) {
30
+ packageRoot = current
31
+ }
32
+ if (fs.existsSync(path.join(current, '.git'))) return packageRoot
33
+ const parent = path.dirname(current)
34
+ if (parent === current) return null
35
+ current = parent
36
+ }
37
+ }
38
+
39
+ export function detectPreferredPackageManager(startDir: string): SetupPackageManager {
40
+ let current = path.resolve(startDir)
41
+ while (true) {
42
+ if (fs.existsSync(path.join(current, 'bun.lock'))) return 'bun'
43
+ if (fs.existsSync(path.join(current, 'bun.lockb'))) return 'bun'
44
+ if (fs.existsSync(path.join(current, 'pnpm-lock.yaml'))) return 'pnpm'
45
+ if (fs.existsSync(path.join(current, 'package-lock.json'))) return 'npm'
46
+ const parent = path.dirname(current)
47
+ if (parent === current) return 'npm'
48
+ current = parent
49
+ }
50
+ }
51
+
52
+ export function planRepositorySetup(input: {
53
+ appDir: string
54
+ cliVersion: string
55
+ }): RepositorySetupPlan {
56
+ const appDir = path.resolve(input.appDir)
57
+ const manifest = readManifest(path.join(appDir, 'package.json'))
58
+ const expectedScript = `${rnxPublicBrand.commandName} open`
59
+ const existingScript = manifest.scripts?.[rnxPublicBrand.commandName]
60
+ if (existingScript && existingScript !== expectedScript) {
61
+ throw new Error(
62
+ `package.json already defines scripts.${rnxPublicBrand.commandName} as ${JSON.stringify(existingScript)}; resolve that conflict before setup`,
63
+ )
64
+ }
65
+
66
+ const packageManager = detectPreferredPackageManager(appDir)
67
+ const operations: RepositoryOperation[] = []
68
+ if (manifest.devDependencies?.[rnxPublicBrand.packageName] !== input.cliVersion) {
69
+ operations.push(addDependencyOperation(packageManager, input.cliVersion))
70
+ }
71
+ if (existingScript !== expectedScript) {
72
+ operations.push(scriptOperation(packageManager))
73
+ }
74
+ return {
75
+ appDir,
76
+ packageManager,
77
+ operations,
78
+ }
79
+ }
80
+
81
+ export async function applyRepositorySetup(
82
+ plan: RepositorySetupPlan,
83
+ options: { dryRun: boolean; json: boolean },
84
+ ): Promise<void> {
85
+ for (const operation of plan.operations) {
86
+ if (options.dryRun) continue
87
+ await runOperation(operation, plan.appDir, options.json)
88
+ }
89
+ }
90
+
91
+ export function formatRepositoryOperations(plan: RepositorySetupPlan): string[] {
92
+ return plan.operations.map(
93
+ (operation) => `${operation.command} ${operation.args.map(quoteArg).join(' ')}`,
94
+ )
95
+ }
96
+
97
+ function readManifest(packagePath: string): PackageManifest {
98
+ let parsed: unknown
99
+ try {
100
+ parsed = JSON.parse(fs.readFileSync(packagePath, 'utf8'))
101
+ } catch (error) {
102
+ const message = error instanceof Error ? error.message : String(error)
103
+ throw new Error(`could not read ${packagePath}: ${message}`)
104
+ }
105
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
106
+ throw new Error(`${packagePath} must contain a JSON object`)
107
+ }
108
+ const scripts = readStringRecord(Reflect.get(parsed, 'scripts'))
109
+ const devDependencies = readStringRecord(Reflect.get(parsed, 'devDependencies'))
110
+ return {
111
+ ...(scripts ? { scripts } : {}),
112
+ ...(devDependencies ? { devDependencies } : {}),
113
+ }
114
+ }
115
+
116
+ function readStringRecord(value: unknown): Record<string, string> | null {
117
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return null
118
+ const entries = Object.entries(value).filter(
119
+ (entry): entry is [string, string] => typeof entry[1] === 'string',
120
+ )
121
+ return Object.fromEntries(entries)
122
+ }
123
+
124
+ function addDependencyOperation(
125
+ packageManager: SetupPackageManager,
126
+ version: string,
127
+ ): RepositoryOperation {
128
+ const spec = `${rnxPublicBrand.packageName}@${version}`
129
+ switch (packageManager) {
130
+ case 'bun':
131
+ return {
132
+ command: 'bun',
133
+ args: ['add', '--dev', '--exact', spec],
134
+ description: `install ${spec} as an exact dev dependency`,
135
+ }
136
+ case 'pnpm':
137
+ return {
138
+ command: 'pnpm',
139
+ args: ['add', '--save-dev', '--save-exact', spec],
140
+ description: `install ${spec} as an exact dev dependency`,
141
+ }
142
+ case 'npm':
143
+ return {
144
+ command: 'npm',
145
+ args: ['install', '--save-dev', '--save-exact', spec],
146
+ description: `install ${spec} as an exact dev dependency`,
147
+ }
148
+ }
149
+ }
150
+
151
+ function quoteArg(value: string): string {
152
+ return /^[a-zA-Z0-9_./@=-]+$/.test(value) ? value : JSON.stringify(value)
153
+ }
154
+
155
+ function runOperation(
156
+ operation: RepositoryOperation,
157
+ cwd: string,
158
+ json: boolean,
159
+ ): Promise<void> {
160
+ return new Promise((resolve, reject) => {
161
+ const child = spawn(operation.command, operation.args, {
162
+ cwd,
163
+ stdio: json ? ['ignore', 2, 'inherit'] : 'inherit',
164
+ })
165
+ child.once('error', reject)
166
+ child.once('exit', (code) => {
167
+ if (code === 0) resolve()
168
+ else reject(new Error(`${operation.command} exited with code ${code ?? 'unknown'}`))
169
+ })
170
+ })
171
+ }
172
+
173
+ function scriptOperation(packageManager: SetupPackageManager): RepositoryOperation {
174
+ return {
175
+ command: packageManager,
176
+ args:
177
+ packageManager === 'bun'
178
+ ? [
179
+ 'pm',
180
+ 'pkg',
181
+ 'set',
182
+ `scripts.${rnxPublicBrand.commandName}=${rnxPublicBrand.commandName} open`,
183
+ ]
184
+ : [
185
+ 'pkg',
186
+ 'set',
187
+ `scripts.${rnxPublicBrand.commandName}=${rnxPublicBrand.commandName} open`,
188
+ ],
189
+ description: `add the \`${rnxPublicBrand.commandName}\` package script`,
190
+ }
191
+ }
@@ -0,0 +1,98 @@
1
+ import {
2
+ RNX_CLOUD_SESSION_ENV,
3
+ RNX_SHELL_UPDATE_SET_PREFIX,
4
+ RNX_SHELL_UPDATE_UNSET,
5
+ } from './cloud-session'
6
+
7
+ export function renderRnxShellInit(shell: string): string {
8
+ if (shell !== 'bash' && shell !== 'zsh') {
9
+ throw new Error('RNX shell integration supports bash and zsh')
10
+ }
11
+ return `rnx() {
12
+ local -a _rnx_shell_args
13
+ local _rnx_shell_command _rnx_shell_create _rnx_shell_update
14
+ local _rnx_shell_session _rnx_shell_status _rnx_shell_traced
15
+ _rnx_shell_args=("$@")
16
+ _rnx_shell_command=''
17
+ _rnx_shell_create=0
18
+ while [ "$#" -gt 0 ]; do
19
+ case "$1" in
20
+ -h|--help|-V|--version|-v|--verbose|--headless) shift ;;
21
+ -p|--port|-d|--device|-t|--theme|--driver|--sim|--session|--tab)
22
+ if [ "$#" -ge 2 ]; then shift 2; else shift; fi
23
+ ;;
24
+ --) break ;;
25
+ -*) shift ;;
26
+ *) _rnx_shell_command="$1"; shift; break ;;
27
+ esac
28
+ done
29
+ case "$_rnx_shell_command" in
30
+ claim|close) ;;
31
+ ios)
32
+ for _rnx_shell_session in "$@"; do
33
+ if [ "$_rnx_shell_session" = '--remote' ]; then
34
+ _rnx_shell_create=1
35
+ break
36
+ fi
37
+ done
38
+ if [ "$_rnx_shell_create" -ne 1 ]; then
39
+ command rnx "\${_rnx_shell_args[@]}"
40
+ return $?
41
+ fi
42
+ ;;
43
+ *)
44
+ command rnx "\${_rnx_shell_args[@]}"
45
+ return $?
46
+ ;;
47
+ esac
48
+
49
+ if { [ -n "\${BASH_VERSION:-}" ] && [ "\${BASH_SUBSHELL:-0}" -ne 0 ]; } ||
50
+ { [ -n "\${ZSH_VERSION:-}" ] && [ "\${ZSH_SUBSHELL:-0}" -ne 0 ]; }; then
51
+ printf '%s\\n' 'rnx remote simulator lifecycle commands must run directly in the current shell, not in a pipeline or command substitution' >&2
52
+ return 1
53
+ fi
54
+ if ( : >&9 ) 2>/dev/null; then
55
+ printf '%s\\n' 'rnx remote simulator shell integration requires file descriptor 9 to be unused' >&2
56
+ return 1
57
+ fi
58
+ if ! ( export ${RNX_CLOUD_SESSION_ENV}=__rnx_shell_probe && unset ${RNX_CLOUD_SESSION_ENV} ) 2>/dev/null; then
59
+ printf '%s\\n' 'rnx remote simulator shell integration cannot update RNX_CLOUD_SESSION in this shell' >&2
60
+ return 1
61
+ fi
62
+
63
+ case "$-" in
64
+ *x*) _rnx_shell_traced=1; set +x ;;
65
+ *) _rnx_shell_traced=0 ;;
66
+ esac
67
+ exec 9>&1
68
+ _rnx_shell_update="$(RNX_SHELL_UPDATE_FD=3 command rnx "\${_rnx_shell_args[@]}" 3>&1 1>&9)"
69
+ _rnx_shell_status=$?
70
+ exec 9>&-
71
+
72
+ if [ "$_rnx_shell_status" -eq 0 ]; then
73
+ case "$_rnx_shell_update" in
74
+ ${RNX_SHELL_UPDATE_SET_PREFIX}*)
75
+ _rnx_shell_session="\${_rnx_shell_update#${RNX_SHELL_UPDATE_SET_PREFIX}}"
76
+ case "$_rnx_shell_session" in
77
+ ''|*$'\\n'*) _rnx_shell_status=1 ;;
78
+ *) export ${RNX_CLOUD_SESSION_ENV}="$_rnx_shell_session" || _rnx_shell_status=$? ;;
79
+ esac
80
+ ;;
81
+ ${RNX_SHELL_UPDATE_UNSET})
82
+ unset ${RNX_CLOUD_SESSION_ENV} || _rnx_shell_status=$?
83
+ ;;
84
+ *) _rnx_shell_status=1 ;;
85
+ esac
86
+ if [ "$_rnx_shell_status" -ne 0 ]; then
87
+ printf '%s\\n' 'rnx refused an invalid shell session update' >&2
88
+ elif [ "$_rnx_shell_create" -eq 1 ]; then
89
+ command rnx __cloud-confirm
90
+ _rnx_shell_status=$?
91
+ fi
92
+ fi
93
+
94
+ if [ "$_rnx_shell_traced" -eq 1 ]; then set -x; fi
95
+ return "$_rnx_shell_status"
96
+ }
97
+ `
98
+ }
@@ -0,0 +1,4 @@
1
+ declare const __SOOTSIM_STANDALONE__: boolean
2
+
3
+ export const IS_STANDALONE =
4
+ typeof __SOOTSIM_STANDALONE__ !== 'undefined' && __SOOTSIM_STANDALONE__
@@ -0,0 +1,84 @@
1
+ import { diagnosticsAllowedForCurrentProcess } from './privacy'
2
+
3
+ type PackageIncompatibility = {
4
+ kind: 'package_incompatibility'
5
+ packageName: string
6
+ packageVersion: string
7
+ unsupportedApi?: string
8
+ }
9
+
10
+ type ErrorShape = {
11
+ kind: 'error_shape'
12
+ errorClass: string
13
+ }
14
+
15
+ export type CliDiagnostic = PackageIncompatibility | ErrorShape
16
+
17
+ type Config = { apiKey: string; host: string }
18
+
19
+ function loadConfig(): Config | null {
20
+ if (!diagnosticsAllowedForCurrentProcess()) return null
21
+ const apiKey =
22
+ process.env.CONTRAST_POSTHOG_API_KEY || process.env.VITE_POSTHOG_API_KEY || ''
23
+ if (!apiKey) return null
24
+ const host =
25
+ process.env.CONTRAST_POSTHOG_HOST ||
26
+ process.env.VITE_POSTHOG_HOST ||
27
+ 'https://us.i.posthog.com'
28
+ return { apiKey, host: host.replace(/\/+$/, '') }
29
+ }
30
+
31
+ function bounded(value: string, max: number): string {
32
+ return value.trim().slice(0, max)
33
+ }
34
+
35
+ export function buildCliDiagnosticEvent(diagnostic: CliDiagnostic) {
36
+ const properties =
37
+ diagnostic.kind === 'package_incompatibility'
38
+ ? {
39
+ package_name: bounded(diagnostic.packageName, 160),
40
+ package_version: bounded(diagnostic.packageVersion, 80),
41
+ ...(diagnostic.unsupportedApi
42
+ ? { unsupported_api: bounded(diagnostic.unsupportedApi, 160) }
43
+ : {}),
44
+ }
45
+ : { error_class: bounded(diagnostic.errorClass, 160) }
46
+
47
+ return {
48
+ event: diagnostic.kind,
49
+ distinct_id: 'rnx-cli-diagnostic',
50
+ properties,
51
+ }
52
+ }
53
+
54
+ const queue: CliDiagnostic[] = []
55
+
56
+ export function trackCliDiagnostic(diagnostic: CliDiagnostic): void {
57
+ if (!diagnosticsAllowedForCurrentProcess()) return
58
+ queue.push(diagnostic)
59
+ }
60
+
61
+ export function trackCliError(error: unknown): void {
62
+ const errorClass = error instanceof Error ? error.constructor.name : typeof error
63
+ trackCliDiagnostic({ kind: 'error_shape', errorClass })
64
+ }
65
+
66
+ export async function flushCliTelemetry(): Promise<void> {
67
+ if (queue.length === 0) return
68
+ const config = loadConfig()
69
+ if (!config) {
70
+ queue.length = 0
71
+ return
72
+ }
73
+ const batch = queue.splice(0, queue.length).map(buildCliDiagnosticEvent)
74
+ try {
75
+ await fetch(`${config.host}/batch/`, {
76
+ method: 'POST',
77
+ headers: { 'content-type': 'application/json' },
78
+ body: JSON.stringify({ api_key: config.apiKey, batch }),
79
+ signal: AbortSignal.timeout(2_000),
80
+ })
81
+ } catch {
82
+ // diagnostics never change a command's result.
83
+ }
84
+ }
package/cli/ui.ts ADDED
@@ -0,0 +1,68 @@
1
+ const RESET = '\x1b[0m'
2
+ const BOLD = '\x1b[1m'
3
+ const DIM = '\x1b[2m'
4
+ const BRAND_BLUE = '\x1b[38;2;42;95;255m'
5
+ const GREEN = '\x1b[38;5;84m'
6
+ const RED = '\x1b[38;5;203m'
7
+
8
+ function paint(code: string, value: string): string {
9
+ if (!process.stdout.isTTY || process.env.NO_COLOR !== undefined) return value
10
+ return `${code}${value}${RESET}`
11
+ }
12
+
13
+ export function accent(value: string): string {
14
+ return paint(BRAND_BLUE, value)
15
+ }
16
+
17
+ export function bold(value: string): string {
18
+ return paint(BOLD, value)
19
+ }
20
+
21
+ export function muted(value: string): string {
22
+ return paint(DIM, value)
23
+ }
24
+
25
+ export function success(value: string): string {
26
+ return paint(GREEN, value)
27
+ }
28
+
29
+ export function failure(value: string): string {
30
+ return paint(RED, value)
31
+ }
32
+
33
+ export function printBrand(subtitle?: string): void {
34
+ console.log()
35
+ console.log(` ${accent('◤◢')} ${bold('rnx')}`)
36
+ if (subtitle) console.log(` ${muted(subtitle)}`)
37
+ }
38
+
39
+ export function printPromptBlock(title: string, description: string[]): void {
40
+ console.log(`\n ${accent('◆')} ${bold(title)}`)
41
+ for (const line of description) console.log(` ${muted(line)}`)
42
+ }
43
+
44
+ export async function withSpinner<T>(label: string, run: () => Promise<T>): Promise<T> {
45
+ if (!process.stdout.isTTY) {
46
+ console.log(` ${label}...`)
47
+ return run()
48
+ }
49
+
50
+ const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
51
+ let index = 0
52
+ const draw = () => {
53
+ process.stdout.write(`\r\x1b[2K ${accent(frames[index++ % frames.length])} ${label}`)
54
+ }
55
+ draw()
56
+ const timer = setInterval(draw, 80)
57
+
58
+ try {
59
+ const result = await run()
60
+ clearInterval(timer)
61
+ process.stdout.write(`\r\x1b[2K ${success('✓')} ${label}\n`)
62
+ return result
63
+ } catch (error) {
64
+ clearInterval(timer)
65
+ process.stdout.write(`\r\x1b[2K ${failure('×')} ${label}\n`)
66
+ throw error
67
+ }
68
+ }