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,206 @@
1
+ interface CloudBoundaryInvocation {
2
+ command: string
3
+ args: string[]
4
+ }
5
+
6
+ export type CloudBoundaryResult = { handled: false } | { handled: true; code: number }
7
+
8
+ const CLOUD_TARGET_COMMANDS = new Set([
9
+ 'assert',
10
+ 'camera',
11
+ 'claim',
12
+ 'close',
13
+ 'debug',
14
+ 'describe',
15
+ 'detox',
16
+ 'do',
17
+ 'film',
18
+ 'find',
19
+ 'get',
20
+ 'logs',
21
+ 'maestro',
22
+ 'mode',
23
+ 'network',
24
+ 'perf',
25
+ 'permissions',
26
+ 'record',
27
+ 'reset',
28
+ 'screenshot',
29
+ 'shell',
30
+ 'state',
31
+ 'storage',
32
+ 'timeline',
33
+ 'wait',
34
+ 'what-happened',
35
+ ])
36
+
37
+ function firstVerb(positional: string[]): string | null {
38
+ return positional.find((value) => !value.startsWith('-')) ?? null
39
+ }
40
+
41
+ const CLOUD_DO_VERBS = new Set([
42
+ 'tap',
43
+ 'tap-id',
44
+ 'tap-text',
45
+ 'tap-best',
46
+ 'double-tap',
47
+ 'long-press',
48
+ 'type',
49
+ 'key',
50
+ 'key-sequence',
51
+ 'scroll',
52
+ 'sleep',
53
+ 'settle',
54
+ ])
55
+
56
+ function doChainVerbs(positional: string[]): string[] {
57
+ const verbs: string[] = []
58
+ let expectingVerb = true
59
+ for (const argument of positional) {
60
+ if (argument === '--then') {
61
+ expectingVerb = true
62
+ continue
63
+ }
64
+ if (expectingVerb && !argument.startsWith('-')) {
65
+ verbs.push(argument)
66
+ expectingVerb = false
67
+ }
68
+ }
69
+ return verbs
70
+ }
71
+
72
+ function supportsCloudDo(positional: string[]): boolean {
73
+ const verbs = doChainVerbs(positional)
74
+ if (verbs.length === 0) return true
75
+ return verbs.every((verb) => CLOUD_DO_VERBS.has(verb))
76
+ }
77
+
78
+ function supportsCloudRead(
79
+ command: string,
80
+ args: string[],
81
+ positional: string[],
82
+ ): boolean {
83
+ if (command === 'describe' || command === 'find') return true
84
+ if (command === 'state') return firstVerb(positional) === null
85
+ if (command === 'get') {
86
+ const verb = firstVerb(positional)
87
+ return verb === 'tree' || verb === 'node' || verb === 'count' || verb === 'memory'
88
+ }
89
+ if (command === 'wait') {
90
+ const verb = firstVerb(positional)
91
+ return verb === 'ready' || verb === 'selector'
92
+ }
93
+ if (command === 'logs') {
94
+ const verb = firstVerb(positional)
95
+ return (
96
+ !args.includes('--tail') &&
97
+ !args.includes('-f') &&
98
+ verb !== 'tail' &&
99
+ verb !== 'clear' &&
100
+ verb !== 'purge'
101
+ )
102
+ }
103
+ if (command === 'screenshot') {
104
+ return !args.some((arg) =>
105
+ ['--area', '--id', '--no-shell', '--shell-only', '--text', '--with-frame'].includes(
106
+ arg,
107
+ ),
108
+ )
109
+ }
110
+ if (command === 'do') return supportsCloudDo(positional)
111
+ if (command === 'reset') return true
112
+ return false
113
+ }
114
+
115
+ function refuseCloudInvocation(
116
+ command: string,
117
+ positional: string[],
118
+ ): CloudBoundaryResult {
119
+ const verb = firstVerb(positional)
120
+ const name = verb ? `${command} ${verb}` : command
121
+ console.error(` rnx ${name} is not available for a remote simulator`)
122
+ return { handled: true, code: 1 }
123
+ }
124
+
125
+ export async function dispatchCloudBoundary({
126
+ command,
127
+ args,
128
+ }: CloudBoundaryInvocation): Promise<CloudBoundaryResult> {
129
+ if ((command === 'ios' || command === 'android') && args.includes('--remote')) {
130
+ return { handled: false }
131
+ }
132
+ if (!CLOUD_TARGET_COMMANDS.has(command)) return { handled: false }
133
+
134
+ const [{ parseBridgeCliArgs }, cloudClient, cloudSession] = await Promise.all([
135
+ import('./ws-bridge'),
136
+ import('./cloud-client'),
137
+ import('./cloud-session'),
138
+ ])
139
+ const parsed = parseBridgeCliArgs(args)
140
+ const positionalTarget =
141
+ command === 'claim' || command === 'close' ? firstVerb(parsed.positional) : null
142
+ const target: Pick<typeof parsed, 'simId' | 'simIdSource'> = positionalTarget
143
+ ? { simId: positionalTarget, simIdSource: 'flag' }
144
+ : parsed
145
+ let session
146
+ try {
147
+ session = cloudClient.resolveCloudSessionForParsed(target)
148
+ } catch (error) {
149
+ console.error(` ${error instanceof Error ? error.message : String(error)}`)
150
+ return { handled: true, code: 1 }
151
+ }
152
+ if (!session) return { handled: false }
153
+
154
+ if (command === 'close') {
155
+ if (args.includes('--all') || args.includes('--others')) {
156
+ return refuseCloudInvocation(command, parsed.positional)
157
+ }
158
+ try {
159
+ const descriptor = cloudSession.requireCloudSessionShellUpdate()
160
+ await cloudClient.closeCloudSession(session)
161
+ cloudSession.publishCloudSessionToShell(null, descriptor)
162
+ console.log(` closed: remote simulator ${session.simId}`)
163
+ return { handled: true, code: 0 }
164
+ } catch (error) {
165
+ console.error(
166
+ ` close failed: ${error instanceof Error ? error.message : String(error)}`,
167
+ )
168
+ return { handled: true, code: 1 }
169
+ }
170
+ }
171
+
172
+ if (command === 'claim') {
173
+ try {
174
+ const descriptor = cloudSession.requireCloudSessionShellUpdate()
175
+ const claimed = await cloudClient.claimCloudSimulator(session, {
176
+ force: args.includes('--force'),
177
+ })
178
+ cloudSession.publishCloudSessionToShell(claimed.session, descriptor)
179
+ console.log(` claimed: ${session.simId}`)
180
+ console.log(` expires: ${claimed.claim.expiresAt}`)
181
+ return { handled: true, code: 0 }
182
+ } catch (error) {
183
+ console.error(
184
+ ` claim failed: ${error instanceof Error ? error.message : String(error)}`,
185
+ )
186
+ return { handled: true, code: 1 }
187
+ }
188
+ }
189
+
190
+ // these inputs use the same perform executor as an inline chain. a CPU
191
+ // simulator has focused text inputs but no browser keyboard to inspect.
192
+ if (
193
+ command === 'do' &&
194
+ supportsCloudDo(parsed.positional) &&
195
+ doChainVerbs(parsed.positional).some(
196
+ (verb) => verb === 'type' || verb === 'key' || verb === 'key-sequence',
197
+ )
198
+ ) {
199
+ const { runDoChain } = await import('./commands/do-chain')
200
+ return { handled: true, code: await runDoChain(args) }
201
+ }
202
+
203
+ return supportsCloudRead(command, args, parsed.positional)
204
+ ? { handled: false }
205
+ : refuseCloudInvocation(command, parsed.positional)
206
+ }
@@ -0,0 +1,227 @@
1
+ import { fstatSync, writeSync } from 'node:fs'
2
+ import { isLoopbackHost } from '../src/backend-origin.ts'
3
+
4
+ const CLOUD_SESSION_VERSION = 3 as const
5
+ const SHELL_UPDATE_FD_ENV = 'RNX_SHELL_UPDATE_FD'
6
+ const MAX_ENCODED_SESSION_BYTES = 32 * 1024
7
+
8
+ export const RNX_CLOUD_SESSION_ENV = 'RNX_CLOUD_SESSION'
9
+ export const RNX_SHELL_UPDATE_PREFIX = '__RNX_SHELL_UPDATE_V1__'
10
+ export const RNX_SHELL_UPDATE_SET_PREFIX = `${RNX_SHELL_UPDATE_PREFIX}set:`
11
+ export const RNX_SHELL_UPDATE_UNSET = `${RNX_SHELL_UPDATE_PREFIX}unset`
12
+
13
+ // the shell's remote target: one simulator on the instance API, addressed by
14
+ // its own id and token.
15
+ export type CloudSession = CloudSimSession
16
+
17
+ export type RemotePlatform = 'ios' | 'android'
18
+
19
+ export interface CloudSessionArtifactIdentity {
20
+ sha256: string
21
+ bytes?: number
22
+ }
23
+
24
+ /**
25
+ * one remote simulator and the claim ordinary commands drive it with. the
26
+ * watch URL the create returned rides along so a reattach reopens the same
27
+ * simulator. a nano-box simulator keeps the box that hosts it; an instance
28
+ * API simulator has no box.
29
+ */
30
+ export interface CloudSimSession {
31
+ version: 3
32
+ service: 'sim'
33
+ simId: string
34
+ apiOrigin: string
35
+ claimId: string
36
+ token: string
37
+ streamUrl?: string | null
38
+ platform?: RemotePlatform
39
+ device?: string
40
+ artifact?: CloudSessionArtifactIdentity
41
+ /** nano-box simulators only: the box that hosts them. absent on the instance API. */
42
+ boxId?: string
43
+ boxToken?: string
44
+ }
45
+
46
+ function isRecord(value: unknown): value is Record<string, unknown> {
47
+ return value !== null && typeof value === 'object' && !Array.isArray(value)
48
+ }
49
+
50
+ export function normalizeCloudOrigin(source: string): string {
51
+ let parsed: URL
52
+ try {
53
+ parsed = new URL(source)
54
+ } catch {
55
+ throw new Error(`invalid remote simulator origin: ${source}`)
56
+ }
57
+ if (
58
+ (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') ||
59
+ parsed.username ||
60
+ parsed.password ||
61
+ parsed.pathname !== '/' ||
62
+ parsed.search ||
63
+ parsed.hash
64
+ ) {
65
+ throw new Error(`remote simulator origin must be an HTTP(S) origin: ${source}`)
66
+ }
67
+ if (parsed.protocol === 'http:' && !isLoopbackHost(parsed.hostname)) {
68
+ throw new Error(`remote simulator origin requires HTTPS outside loopback: ${source}`)
69
+ }
70
+ return parsed.origin
71
+ }
72
+
73
+ export function createCloudSession(input: {
74
+ simId: string
75
+ apiOrigin: string
76
+ claimId: string
77
+ token: string
78
+ streamUrl?: string | null
79
+ platform?: RemotePlatform
80
+ device?: string
81
+ artifact?: CloudSessionArtifactIdentity
82
+ boxId?: string
83
+ boxToken?: string
84
+ }): CloudSimSession {
85
+ const boxId = input.boxId?.trim() ?? ''
86
+ const boxToken = input.boxToken ?? ''
87
+ if ((boxId === '') !== (boxToken === '')) {
88
+ throw new Error('cloud session names a box with both its id and its token')
89
+ }
90
+ const session: CloudSimSession = {
91
+ version: CLOUD_SESSION_VERSION,
92
+ service: 'sim',
93
+ simId: input.simId.trim(),
94
+ apiOrigin: normalizeCloudOrigin(input.apiOrigin.trim()),
95
+ claimId: input.claimId.trim(),
96
+ token: input.token,
97
+ ...(input.streamUrl ? { streamUrl: input.streamUrl } : {}),
98
+ ...(input.platform ? { platform: input.platform } : {}),
99
+ ...(input.device?.trim() ? { device: input.device.trim() } : {}),
100
+ ...(input.artifact ? { artifact: input.artifact } : {}),
101
+ ...(boxId && boxToken ? { boxId, boxToken } : {}),
102
+ }
103
+ if (!session.simId || !session.claimId || !session.token) {
104
+ throw new Error('cloud session requires a simulator, an origin, a claim, and a token')
105
+ }
106
+ return session
107
+ }
108
+
109
+ export function serializeCloudSession(session: CloudSession): string {
110
+ const validated = createCloudSession(session)
111
+ return Buffer.from(JSON.stringify(validated), 'utf8').toString('base64url')
112
+ }
113
+
114
+ export function parseCloudSession(encoded: string): CloudSession {
115
+ const value = encoded.trim()
116
+ if (
117
+ !value ||
118
+ value.length > MAX_ENCODED_SESSION_BYTES ||
119
+ !/^[A-Za-z0-9_-]+$/.test(value)
120
+ ) {
121
+ throw new Error(`${RNX_CLOUD_SESSION_ENV} is invalid`)
122
+ }
123
+ let parsed: unknown
124
+ try {
125
+ const bytes = Buffer.from(value, 'base64url')
126
+ if (bytes.toString('base64url') !== value) {
127
+ throw new Error('non-canonical cloud session')
128
+ }
129
+ parsed = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(bytes))
130
+ } catch {
131
+ throw new Error(`${RNX_CLOUD_SESSION_ENV} is invalid`)
132
+ }
133
+ if (!isRecord(parsed) || parsed.version !== CLOUD_SESSION_VERSION) {
134
+ throw new Error(`${RNX_CLOUD_SESSION_ENV} is invalid`)
135
+ }
136
+ const apiOrigin = parsed.apiOrigin
137
+ if (typeof apiOrigin !== 'string') {
138
+ throw new Error(`${RNX_CLOUD_SESSION_ENV} is invalid`)
139
+ }
140
+ const platform =
141
+ parsed.platform === 'ios' || parsed.platform === 'android'
142
+ ? parsed.platform
143
+ : undefined
144
+ const device = typeof parsed.device === 'string' ? parsed.device : undefined
145
+ const artifact =
146
+ isRecord(parsed.artifact) && typeof parsed.artifact.sha256 === 'string'
147
+ ? {
148
+ sha256: parsed.artifact.sha256,
149
+ bytes:
150
+ typeof parsed.artifact.bytes === 'number' ? parsed.artifact.bytes : undefined,
151
+ }
152
+ : undefined
153
+ const streamUrl = typeof parsed.streamUrl === 'string' ? parsed.streamUrl : undefined
154
+ const boxId = typeof parsed.boxId === 'string' ? parsed.boxId : undefined
155
+ const boxToken = typeof parsed.boxToken === 'string' ? parsed.boxToken : undefined
156
+ try {
157
+ const simId = parsed.simId
158
+ const claimId = parsed.claimId
159
+ const token = parsed.token
160
+ if (
161
+ parsed.service !== 'sim' ||
162
+ typeof simId !== 'string' ||
163
+ typeof claimId !== 'string' ||
164
+ typeof token !== 'string'
165
+ ) {
166
+ throw new Error('unreadable cloud session')
167
+ }
168
+ return createCloudSession({
169
+ simId,
170
+ apiOrigin,
171
+ claimId,
172
+ token,
173
+ streamUrl,
174
+ platform,
175
+ device,
176
+ artifact,
177
+ boxId,
178
+ boxToken,
179
+ })
180
+ } catch {
181
+ throw new Error(`${RNX_CLOUD_SESSION_ENV} is invalid`)
182
+ }
183
+ }
184
+
185
+ export function readCloudSession(
186
+ environment: Readonly<Record<string, string | undefined>> = process.env,
187
+ ): CloudSession | null {
188
+ const encoded = environment[RNX_CLOUD_SESSION_ENV]
189
+ return encoded?.trim() ? parseCloudSession(encoded) : null
190
+ }
191
+
192
+ function shellUpdateFd(
193
+ environment: Readonly<Record<string, string | undefined>> = process.env,
194
+ ): number | null {
195
+ const source = environment[SHELL_UPDATE_FD_ENV]
196
+ if (!source) return null
197
+ const descriptor = Number(source)
198
+ if (!Number.isSafeInteger(descriptor) || descriptor < 3) {
199
+ throw new Error('RNX shell integration provided an invalid update channel')
200
+ }
201
+ try {
202
+ fstatSync(descriptor)
203
+ } catch {
204
+ throw new Error('RNX shell integration update channel is unavailable')
205
+ }
206
+ return descriptor
207
+ }
208
+
209
+ export function requireCloudSessionShellUpdate(): number {
210
+ const descriptor = shellUpdateFd()
211
+ if (descriptor === null) {
212
+ throw new Error(
213
+ 'RNX shell integration is required so this remote simulator stays connected to the current shell; restart the shell after installing RNX',
214
+ )
215
+ }
216
+ return descriptor
217
+ }
218
+
219
+ export function publishCloudSessionToShell(
220
+ session: CloudSession | null,
221
+ descriptor = requireCloudSessionShellUpdate(),
222
+ ): void {
223
+ const update = session
224
+ ? `${RNX_SHELL_UPDATE_SET_PREFIX}${serializeCloudSession(session)}`
225
+ : RNX_SHELL_UPDATE_UNSET
226
+ writeSync(descriptor, `${update}\n`)
227
+ }
@@ -0,0 +1,221 @@
1
+ export type RnxBackendKind = 'node' | 'browser'
2
+
3
+ export interface CommandWriter {
4
+ stdout(text: string): void
5
+ stderr(text: string): void
6
+ }
7
+
8
+ export interface SemanticInvocation {
9
+ tool: string
10
+ input: unknown
11
+ success: boolean
12
+ details?: unknown
13
+ }
14
+
15
+ export interface ImageCommandArtifact {
16
+ kind: 'image'
17
+ name: string
18
+ mediaType: string
19
+ data: Uint8Array | string
20
+ runtimeClaim?: unknown
21
+ }
22
+
23
+ export interface FileCommandArtifact {
24
+ kind: 'file'
25
+ path: string
26
+ mediaType?: string
27
+ runtimeClaim?: unknown
28
+ }
29
+
30
+ export interface RuntimeCommandArtifact {
31
+ kind: 'runtime'
32
+ runtimeClaim: unknown
33
+ }
34
+
35
+ export interface UrlCommandArtifact {
36
+ kind: 'url'
37
+ url: string
38
+ label?: string
39
+ runtimeClaim?: unknown
40
+ }
41
+
42
+ export type CommandArtifact =
43
+ | ImageCommandArtifact
44
+ | FileCommandArtifact
45
+ | RuntimeCommandArtifact
46
+ | UrlCommandArtifact
47
+
48
+ export interface CommandExecutionResult {
49
+ exitCode: number
50
+ stdout: string
51
+ stderr: string
52
+ invocations: SemanticInvocation[]
53
+ artifacts: CommandArtifact[]
54
+ }
55
+
56
+ export interface RnxInvocationBase {
57
+ command: string
58
+ args: string[]
59
+ globalFlags: Record<string, string | boolean | number>
60
+ }
61
+
62
+ export interface RnxParsedCommandInput {
63
+ command: string
64
+ args: string[]
65
+ globalFlags: Record<string, string | boolean | number>
66
+ }
67
+
68
+ export interface RnxPreviewInvocation extends RnxInvocationBase {
69
+ family: 'preview'
70
+ command: 'web' | 'ios' | 'android'
71
+ platform: 'web' | 'ios' | 'android'
72
+ selection: 'select-or-ensure-existing'
73
+ }
74
+
75
+ export interface RnxRuntimeInvocation extends RnxInvocationBase {
76
+ family: 'runtime'
77
+ }
78
+
79
+ export interface RnxTreeInvocation extends RnxInvocationBase {
80
+ family: 'tree'
81
+ }
82
+
83
+ export interface RnxLayoutInvocation extends RnxInvocationBase {
84
+ family: 'layout'
85
+ }
86
+
87
+ export interface RnxFindInvocation extends RnxInvocationBase {
88
+ family: 'find'
89
+ command: 'find'
90
+ }
91
+
92
+ export interface RnxWaitInvocation extends RnxInvocationBase {
93
+ family: 'wait'
94
+ command: 'wait'
95
+ }
96
+
97
+ export interface RnxActionInvocation extends RnxInvocationBase {
98
+ family: 'action'
99
+ }
100
+
101
+ export interface RnxScreenshotInvocation extends RnxInvocationBase {
102
+ family: 'screenshot'
103
+ command: 'screenshot'
104
+ }
105
+
106
+ export interface RnxLogsInvocation extends RnxInvocationBase {
107
+ family: 'logs'
108
+ }
109
+
110
+ export interface RnxNetworkInvocation extends RnxInvocationBase {
111
+ family: 'network'
112
+ }
113
+
114
+ export interface RnxStorageInvocation extends RnxInvocationBase {
115
+ family: 'storage'
116
+ command: 'storage'
117
+ }
118
+
119
+ export interface RnxHistoryInvocation extends RnxInvocationBase {
120
+ family: 'history'
121
+ command: 'timeline' | 'what-happened'
122
+ }
123
+
124
+ export interface RnxEvidenceInvocation extends RnxInvocationBase {
125
+ family: 'evidence'
126
+ }
127
+
128
+ export type RnxCommandInvocation =
129
+ | RnxPreviewInvocation
130
+ | RnxRuntimeInvocation
131
+ | RnxTreeInvocation
132
+ | RnxLayoutInvocation
133
+ | RnxFindInvocation
134
+ | RnxWaitInvocation
135
+ | RnxActionInvocation
136
+ | RnxScreenshotInvocation
137
+ | RnxLogsInvocation
138
+ | RnxNetworkInvocation
139
+ | RnxStorageInvocation
140
+ | RnxHistoryInvocation
141
+ | RnxEvidenceInvocation
142
+
143
+ export interface RnxExecutionContext {
144
+ writer: CommandWriter
145
+ signal: AbortSignal
146
+ }
147
+
148
+ export interface RnxBackendExecution {
149
+ exitCode: number
150
+ invocations: SemanticInvocation[]
151
+ artifacts: CommandArtifact[]
152
+ }
153
+
154
+ export interface RnxCommandBackend {
155
+ readonly kind: RnxBackendKind
156
+ execute(
157
+ invocation: RnxCommandInvocation,
158
+ context: RnxExecutionContext,
159
+ ): Promise<RnxBackendExecution>
160
+ }
161
+
162
+ export interface RnxBrowserOperations {
163
+ selectPreview(
164
+ invocation: RnxPreviewInvocation,
165
+ context: RnxExecutionContext,
166
+ ): Promise<RnxBackendExecution>
167
+ readRuntime(
168
+ invocation: RnxRuntimeInvocation,
169
+ context: RnxExecutionContext,
170
+ ): Promise<RnxBackendExecution>
171
+ inspectTree(
172
+ invocation: RnxTreeInvocation,
173
+ context: RnxExecutionContext,
174
+ ): Promise<RnxBackendExecution>
175
+ inspectLayout(
176
+ invocation: RnxLayoutInvocation,
177
+ context: RnxExecutionContext,
178
+ ): Promise<RnxBackendExecution>
179
+ find(
180
+ invocation: RnxFindInvocation,
181
+ context: RnxExecutionContext,
182
+ ): Promise<RnxBackendExecution>
183
+ wait(
184
+ invocation: RnxWaitInvocation,
185
+ context: RnxExecutionContext,
186
+ ): Promise<RnxBackendExecution>
187
+ act(
188
+ invocation: RnxActionInvocation,
189
+ context: RnxExecutionContext,
190
+ ): Promise<RnxBackendExecution>
191
+ screenshot(
192
+ invocation: RnxScreenshotInvocation,
193
+ context: RnxExecutionContext,
194
+ ): Promise<RnxBackendExecution>
195
+ readLogs(
196
+ invocation: RnxLogsInvocation,
197
+ context: RnxExecutionContext,
198
+ ): Promise<RnxBackendExecution>
199
+ readNetwork(
200
+ invocation: RnxNetworkInvocation,
201
+ context: RnxExecutionContext,
202
+ ): Promise<RnxBackendExecution>
203
+ accessStorage(
204
+ invocation: RnxStorageInvocation,
205
+ context: RnxExecutionContext,
206
+ ): Promise<RnxBackendExecution>
207
+ readHistory(
208
+ invocation: RnxHistoryInvocation,
209
+ context: RnxExecutionContext,
210
+ ): Promise<RnxBackendExecution>
211
+ collectEvidence(
212
+ invocation: RnxEvidenceInvocation,
213
+ context: RnxExecutionContext,
214
+ ): Promise<RnxBackendExecution>
215
+ }
216
+
217
+ export interface RunRnxCoreOptions {
218
+ backend: RnxCommandBackend
219
+ signal?: AbortSignal
220
+ writer?: CommandWriter
221
+ }