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,67 @@
1
+ import { runCpuProfile } from './cpu-profile'
2
+ import { runInspect } from './inspect'
3
+ import { runReact } from './react'
4
+
5
+ interface PerfOptions {
6
+ port?: number
7
+ verbose?: boolean
8
+ }
9
+
10
+ const VALUE_FLAGS = new Set(['--sim', '--port', '-p'])
11
+ const SUBCOMMANDS = new Set(['cpu', 'react', 'shell', 'scroll'])
12
+
13
+ function findSubcommand(args: string[]): { name: string; index: number } | null {
14
+ for (let i = 0; i < args.length; i++) {
15
+ const arg = args[i]
16
+ if (arg.startsWith('-')) {
17
+ if (VALUE_FLAGS.has(arg)) i++
18
+ continue
19
+ }
20
+ return SUBCOMMANDS.has(arg) ? { name: arg, index: i } : null
21
+ }
22
+ return null
23
+ }
24
+
25
+ function withoutSubcommand(args: string[], index: number): string[] {
26
+ return [...args.slice(0, index), ...args.slice(index + 1)]
27
+ }
28
+
29
+ function printHelp(): void {
30
+ console.log(`
31
+ rnx perf — performance profiling tools
32
+
33
+ usage:
34
+ rnx perf cpu [options]
35
+ rnx perf react <subcommand> [options]
36
+ rnx perf shell <start|stop|transition> [options]
37
+ rnx perf scroll <start|stop> [options]
38
+
39
+ examples:
40
+ rnx perf cpu --duration 5 --output /tmp/trace.cpuprofile
41
+ # writes page + every worker profile and a role-labelled manifest
42
+ rnx perf react summary --limit 10
43
+ rnx perf shell start
44
+ rnx perf shell stop --json
45
+ rnx perf scroll start
46
+ rnx perf scroll stop --json
47
+ `)
48
+ }
49
+
50
+ export async function runPerf(args: string[], opts: PerfOptions): Promise<number> {
51
+ const sub = findSubcommand(args)
52
+ if (!sub) {
53
+ printHelp()
54
+ return args.includes('--help') || args.includes('-h') ? 0 : 1
55
+ }
56
+
57
+ const rest = withoutSubcommand(args, sub.index)
58
+ if (sub.name === 'cpu') return runCpuProfile(rest, opts)
59
+ if (sub.name === 'react') return runReact(rest, opts)
60
+
61
+ const inspectArgs = rest.includes('--help') || rest.includes('-h') ? [] : rest
62
+ await runInspect(['debug', 'perf', ...inspectArgs], {
63
+ ...opts,
64
+ internalPerfCommand: sub.name === 'scroll' ? 'scroll' : 'shell',
65
+ })
66
+ return typeof process.exitCode === 'number' ? process.exitCode : 0
67
+ }
@@ -0,0 +1,203 @@
1
+ import {
2
+ callInBridge,
3
+ callInBridgeWrite,
4
+ createBridgeFromParsed,
5
+ parseBridgeCliArgs,
6
+ } from '../ws-bridge'
7
+ import type {
8
+ AppSettingsSnapshot,
9
+ SystemPermissionApp,
10
+ SystemPermissionKind,
11
+ SystemPermissionMutation,
12
+ SystemPermissionStatus,
13
+ } from '@rnx/globals'
14
+
15
+ interface PermissionsOptions {
16
+ port?: number
17
+ verbose?: boolean
18
+ }
19
+
20
+ const STATUSES: readonly SystemPermissionStatus[] = [
21
+ 'not-determined',
22
+ 'restricted',
23
+ 'denied',
24
+ 'authorized',
25
+ 'limited',
26
+ 'provisional',
27
+ 'ephemeral',
28
+ 'unavailable',
29
+ ]
30
+
31
+ const KINDS: readonly SystemPermissionKind[] = [
32
+ 'app-tracking-transparency',
33
+ 'bluetooth',
34
+ 'calendars',
35
+ 'camera',
36
+ 'contacts',
37
+ 'face-id',
38
+ 'local-network',
39
+ 'location-always',
40
+ 'location-when-in-use',
41
+ 'media-library',
42
+ 'microphone',
43
+ 'motion',
44
+ 'notifications',
45
+ 'photo-library',
46
+ 'photo-library-add-only',
47
+ 'reminders',
48
+ 'siri',
49
+ 'speech-recognition',
50
+ ]
51
+
52
+ function printHelp(): void {
53
+ console.log(`
54
+ rnx permissions — inspect and change simulated operating-system permissions
55
+
56
+ usage:
57
+ rnx permissions list [--json]
58
+ rnx permissions get <app-id> [--json]
59
+ rnx permissions set <app-id> <kind> <status> [--once] [--accuracy full|reduced] [--json]
60
+ rnx permissions reset <app-id> [kind] [--json]
61
+ `)
62
+ }
63
+
64
+ function print(value: unknown, json: boolean): void {
65
+ console.log(json ? JSON.stringify(value, null, 2) : value)
66
+ }
67
+
68
+ function flagValue(args: string[], flag: string): string | undefined {
69
+ const index = args.indexOf(flag)
70
+ return index >= 0 ? args[index + 1] : undefined
71
+ }
72
+
73
+ function permissionKind(value: string): SystemPermissionKind | null {
74
+ return KINDS.find((candidate) => candidate === value) ?? null
75
+ }
76
+
77
+ function permissionStatus(value: string): SystemPermissionStatus | null {
78
+ return STATUSES.find((candidate) => candidate === value) ?? null
79
+ }
80
+
81
+ function mutationFor(
82
+ status: SystemPermissionStatus,
83
+ once: boolean,
84
+ accuracy: 'full' | 'reduced' | undefined,
85
+ ): SystemPermissionMutation {
86
+ switch (status) {
87
+ case 'authorized':
88
+ return {
89
+ status,
90
+ ...(once ? { authorizationScope: 'once' } : null),
91
+ ...(accuracy ? { locationAccuracy: accuracy } : null),
92
+ }
93
+ case 'not-determined':
94
+ case 'restricted':
95
+ case 'denied':
96
+ case 'limited':
97
+ case 'provisional':
98
+ case 'ephemeral':
99
+ case 'unavailable':
100
+ return { status }
101
+ }
102
+ }
103
+
104
+ export async function runPermissions(
105
+ args: string[],
106
+ opts: PermissionsOptions,
107
+ ): Promise<number> {
108
+ if (args.includes('--help') || args.includes('-h')) {
109
+ printHelp()
110
+ return 0
111
+ }
112
+ const parsed = parseBridgeCliArgs(args, {
113
+ port: opts.port,
114
+ stripBooleanFlags: ['--json', '--once'],
115
+ stripValueFlags: ['--accuracy'],
116
+ })
117
+ const [command, appId, kindValue, statusValue] = parsed.positional
118
+ if (!command) {
119
+ printHelp()
120
+ return 1
121
+ }
122
+ const json = args.includes('--json')
123
+ const bridge = createBridgeFromParsed(parsed)
124
+ try {
125
+ if (command === 'list') {
126
+ const apps = await callInBridge<readonly SystemPermissionApp[]>(
127
+ bridge,
128
+ 'SootSim.bridges.test.permissionListApps',
129
+ )
130
+ print(
131
+ json ? apps : apps.map((app) => `${app.appId}\t${app.displayName}`).join('\n'),
132
+ json,
133
+ )
134
+ return 0
135
+ }
136
+ if (!appId) throw new Error(`${command} requires an app id`)
137
+ if (command === 'get') {
138
+ const snapshot = await callInBridge<AppSettingsSnapshot>(
139
+ bridge,
140
+ 'SootSim.bridges.test.permissionGetAppSettings',
141
+ appId,
142
+ )
143
+ print(snapshot, json)
144
+ return 0
145
+ }
146
+ if (command === 'reset') {
147
+ if (kindValue) {
148
+ const kind = permissionKind(kindValue)
149
+ if (!kind) throw new Error(`unknown permission kind: ${kindValue}`)
150
+ await callInBridgeWrite<AppSettingsSnapshot>(
151
+ bridge,
152
+ 'SootSim.bridges.test.permissionSetAppPermission',
153
+ appId,
154
+ kind,
155
+ { status: 'not-determined' },
156
+ )
157
+ } else {
158
+ await callInBridgeWrite<void>(
159
+ bridge,
160
+ 'SootSim.bridges.test.permissionResetApp',
161
+ appId,
162
+ )
163
+ }
164
+ const snapshot = await callInBridge<AppSettingsSnapshot>(
165
+ bridge,
166
+ 'SootSim.bridges.test.permissionGetAppSettings',
167
+ appId,
168
+ )
169
+ print(snapshot, json)
170
+ return 0
171
+ }
172
+ if (command !== 'set') throw new Error(`unknown permissions command: ${command}`)
173
+ if (!kindValue || !statusValue) {
174
+ throw new Error('set requires an app id, permission kind, and status')
175
+ }
176
+ const kind = permissionKind(kindValue)
177
+ if (!kind) throw new Error(`unknown permission kind: ${kindValue}`)
178
+ const status = permissionStatus(statusValue)
179
+ if (!status) throw new Error(`unknown permission status: ${statusValue}`)
180
+ const accuracyValue = flagValue(args, '--accuracy')
181
+ const accuracy =
182
+ accuracyValue === 'full' || accuracyValue === 'reduced' ? accuracyValue : undefined
183
+ if (accuracyValue && !accuracy) {
184
+ throw new Error(`unknown location accuracy: ${accuracyValue}`)
185
+ }
186
+ const snapshot = await callInBridgeWrite<AppSettingsSnapshot>(
187
+ bridge,
188
+ 'SootSim.bridges.test.permissionSetAppPermission',
189
+ appId,
190
+ kind,
191
+ mutationFor(status, args.includes('--once'), accuracy),
192
+ )
193
+ print(snapshot, json)
194
+ return 0
195
+ } catch (error) {
196
+ console.error(
197
+ ` permissions failed: ${error instanceof Error ? error.message : error}`,
198
+ )
199
+ return 1
200
+ } finally {
201
+ bridge.close()
202
+ }
203
+ }
@@ -0,0 +1,410 @@
1
+ // `rnx ios` and `rnx android` — start a simulator on that platform.
2
+ //
3
+ // the platform picks the device; the command context picks where the sim runs
4
+ // (this machine, a local box, or the `rnx remote` namespace). this file resolves
5
+ // the device and hands the rest to the normal open path.
6
+
7
+ import {
8
+ DEFAULT_DEVICE_BY_PLATFORM,
9
+ devices,
10
+ getRuntimePlatform,
11
+ listSelectableDeviceModels,
12
+ type DeviceModel,
13
+ type RuntimePlatform,
14
+ } from 'sootsim-engine/settings'
15
+ import { settingsStore } from 'sootsim-engine/settings/store'
16
+ import { rnxPublicBrand } from '../../src/public-brand'
17
+ import { rethrowIfExit } from '../run-rnx'
18
+ import type { CloudSession } from '../cloud-session'
19
+
20
+ const PLATFORM_LABEL: Record<RuntimePlatform, string> = {
21
+ ios: 'iOS',
22
+ android: 'Android',
23
+ }
24
+
25
+ function isDeviceModel(value: string): value is DeviceModel {
26
+ return value in devices
27
+ }
28
+
29
+ // flags this command reads for itself rather than treating as the bundle.
30
+ const REMOTE_VALUE_FLAGS = new Set(['--device'])
31
+
32
+ function remoteInputFromArgs(
33
+ args: string[],
34
+ platform: RuntimePlatform,
35
+ ): {
36
+ bundlePath: string
37
+ reuse: boolean
38
+ fresh: boolean
39
+ } {
40
+ const positional: string[] = []
41
+ for (let index = 0; index < args.length; index++) {
42
+ const arg = args[index]
43
+ if (arg === '--remote' || arg === '--reuse' || arg === '--new') continue
44
+ if (REMOTE_VALUE_FLAGS.has(arg)) {
45
+ const value = args[++index] ?? null
46
+ if (!value) throw new Error(`rnx remote ${platform} ${arg} requires a value`)
47
+ continue
48
+ }
49
+ const assigned = arg.match(/^(--[a-z-]+)=(.*)$/)
50
+ if (assigned && REMOTE_VALUE_FLAGS.has(assigned[1] ?? '')) {
51
+ const value = assigned[2] ?? ''
52
+ if (!value)
53
+ throw new Error(`rnx remote ${platform} ${assigned[1]} requires a value`)
54
+ continue
55
+ }
56
+ if (
57
+ arg === '--sim' ||
58
+ arg === '--session' ||
59
+ arg === '--tab' ||
60
+ arg.startsWith('--sim=') ||
61
+ arg.startsWith('--session=') ||
62
+ arg.startsWith('--tab=')
63
+ ) {
64
+ throw new Error(
65
+ `rnx remote ${platform} creates a new simulator and cannot target --sim`,
66
+ )
67
+ }
68
+ if (arg.startsWith('-')) {
69
+ throw new Error(`rnx remote ${platform} does not support ${arg}`)
70
+ }
71
+ positional.push(arg)
72
+ }
73
+ if (positional.length !== 1 || !positional[0]) {
74
+ throw new Error(
75
+ `usage: rnx remote ${platform} <local bundle.js|https://bundle-url> [--device <model>] [--reuse|--new]`,
76
+ )
77
+ }
78
+ const reuse = args.includes('--reuse')
79
+ const fresh = args.includes('--new')
80
+ if (reuse && fresh) {
81
+ throw new Error(`rnx remote ${platform} cannot combine --reuse and --new`)
82
+ }
83
+ return { bundlePath: positional[0], reuse, fresh }
84
+ }
85
+
86
+ export async function runPlatformCommand(
87
+ platform: RuntimePlatform,
88
+ args: string[],
89
+ opts: { port?: number } = {},
90
+ ): Promise<number | undefined> {
91
+ // `--device` is applied to the settings store before dispatch, so a
92
+ // contradiction shows up here as a platform mismatch. refuse it by name
93
+ // rather than silently overriding whichever one the user meant.
94
+ const assignedDevice = args.find((arg) => arg.startsWith('--device='))
95
+ const explicitDevice =
96
+ args.find((_, i) => args[i - 1] === '--device') ?? assignedDevice?.slice(9)
97
+ let selectedDevice: DeviceModel
98
+ if (explicitDevice !== undefined) {
99
+ if (!isDeviceModel(explicitDevice)) {
100
+ console.error(
101
+ ` ${rnxPublicBrand.commandName} ${platform}: unknown device "${explicitDevice}"\n` +
102
+ ` run \`${rnxPublicBrand.commandName} device list\` to see valid models`,
103
+ )
104
+ return 1
105
+ }
106
+ const devicePlatform = getRuntimePlatform(explicitDevice)
107
+ if (devicePlatform !== platform) {
108
+ console.error(
109
+ ` ${rnxPublicBrand.commandName} ${platform}: --device ${explicitDevice} is ` +
110
+ `${PLATFORM_LABEL[devicePlatform]}, not ${PLATFORM_LABEL[platform]}.\n` +
111
+ ` pick one of: ${listSelectableDeviceModels()
112
+ .filter((model) => getRuntimePlatform(model) === platform)
113
+ .join(', ')}`,
114
+ )
115
+ return 1
116
+ }
117
+ selectedDevice = explicitDevice
118
+ } else {
119
+ // keep the user's own device when it already belongs to the platform they
120
+ // asked for, so `rnx ios` after `rnx device set iphone-16` does not
121
+ // silently snap back to the default model.
122
+ const current = settingsStore.get('deviceModel')
123
+ if (getRuntimePlatform(current) !== platform) {
124
+ settingsStore.apply({ deviceModel: DEFAULT_DEVICE_BY_PLATFORM[platform] })
125
+ selectedDevice = DEFAULT_DEVICE_BY_PLATFORM[platform]
126
+ } else {
127
+ selectedDevice = current
128
+ }
129
+ }
130
+
131
+ if (args.includes('--remote')) {
132
+ try {
133
+ return await runRemoteCreate(platform, args, selectedDevice)
134
+ } catch (error) {
135
+ rethrowIfExit(error)
136
+ console.error(
137
+ ` ${rnxPublicBrand.commandName} ${platform} --remote failed: ${
138
+ error instanceof Error ? error.message : String(error)
139
+ }`,
140
+ )
141
+ return 1
142
+ }
143
+ }
144
+
145
+ // leftover `--*` tokens are not bundle targets. parse the same flags `open`
146
+ // consumes, then refuse anything still starting with `-`.
147
+ const { parseBridgeCliArgs } = await import('../ws-bridge')
148
+ const leftover = parseBridgeCliArgs(args, {
149
+ port: opts.port,
150
+ stripBooleanFlags: [
151
+ '--new',
152
+ '--headless',
153
+ '--headed',
154
+ '--ephemeral',
155
+ '--hot',
156
+ '--no-hmr',
157
+ '--no-describe',
158
+ '--quiet',
159
+ '--remote',
160
+ ],
161
+ stripValueFlags: [
162
+ '--base-url',
163
+ '--replace',
164
+ '--driver',
165
+ '--profile',
166
+ '--cdp-port',
167
+ '--device',
168
+ '--viewport',
169
+ '--remap',
170
+ ],
171
+ }).positional.find((value) => value.startsWith('-'))
172
+ if (leftover) {
173
+ console.error(` unknown flag: ${leftover}`)
174
+ return 1
175
+ }
176
+
177
+ const { runOpenCommand } = await import('./control')
178
+ await runOpenCommand(args, { port: opts.port })
179
+ return 0
180
+ }
181
+
182
+ /** Runs the existing remote session path without changing the local platform commands. */
183
+ export function runRemotePlatformCommand(
184
+ platform: RuntimePlatform,
185
+ args: string[],
186
+ opts: { port?: number } = {},
187
+ ): Promise<number | undefined> {
188
+ return runPlatformCommand(platform, ['--remote', ...args], opts)
189
+ }
190
+
191
+ const REMOTE_COMMANDS_HELP =
192
+ ' commands: describe, find, get tree|node|count|memory, wait ready|selector, do, reset, logs, state, screenshot'
193
+
194
+ /**
195
+ * `rnx remote ios|android <bundle>` on the one customer plane: one typed
196
+ * create through the instance API for both platforms, and a reattach to the
197
+ * shell's own simulator when it already runs this bundle. the box stays the
198
+ * host behind the API and is never created or addressed here.
199
+ */
200
+ async function runRemoteCreate(
201
+ platform: RuntimePlatform,
202
+ args: string[],
203
+ selectedDevice: string,
204
+ ): Promise<number> {
205
+ const input = remoteInputFromArgs(args, platform)
206
+ const [
207
+ { authHeaderOrExit, cloudAccountIdOrExit },
208
+ {
209
+ claimCloudSimulator,
210
+ closeCloudSession,
211
+ createRemoteSim,
212
+ getRemoteSim,
213
+ produceCloudArtifact,
214
+ },
215
+ {
216
+ createCloudSession,
217
+ publishCloudSessionToShell,
218
+ readCloudSession,
219
+ requireCloudSessionShellUpdate,
220
+ },
221
+ { openInBrowser },
222
+ ] = await Promise.all([
223
+ import('../auth'),
224
+ import('../cloud-client'),
225
+ import('../cloud-session'),
226
+ import('./box'),
227
+ ])
228
+ const artifact = await produceCloudArtifact(input.bundlePath)
229
+ const descriptor = requireCloudSessionShellUpdate()
230
+ const { auth, header } = authHeaderOrExit(`${platform} --remote`)
231
+ const accountId = cloudAccountIdOrExit(auth)
232
+
233
+ const existingSession = readCloudSession()
234
+ if (
235
+ existingSession &&
236
+ !input.fresh &&
237
+ existingSession.platform === platform &&
238
+ existingSession.artifact?.sha256 === artifact.sha256
239
+ ) {
240
+ // an api key reads the simulator back through the account, which mints a
241
+ // fresh watch url with the read. any other credential rereads with the
242
+ // simulator's own token and keeps the stored watch url.
243
+ const live = await getRemoteSim({
244
+ apiOrigin: existingSession.apiOrigin,
245
+ simId: existingSession.simId,
246
+ authorization: auth.kind === 'api-key' ? header : `Bearer ${existingSession.token}`,
247
+ })
248
+ if (live && (live.status === 'open' || live.status === 'hibernated')) {
249
+ const claimed = await claimCloudSimulator(existingSession)
250
+ const streamUrl = live.streamUrl ?? existingSession.streamUrl ?? null
251
+ const session = createCloudSession({
252
+ ...existingSession,
253
+ claimId: claimed.claim.id,
254
+ ...(streamUrl ? { streamUrl } : {}),
255
+ })
256
+ publishCloudSessionToShell(session, descriptor)
257
+ console.log(
258
+ ` reconnected to remote simulator: ${existingSession.simId} (${platform})`,
259
+ )
260
+ console.log(` claim expires: ${claimed.claim.expiresAt}`)
261
+ console.log(REMOTE_COMMANDS_HELP)
262
+ return 0
263
+ }
264
+ }
265
+
266
+ // the new session supersedes the old one, so the shell update channel
267
+ // carries exactly one update: the shell accepts nothing else.
268
+ if (existingSession) await closeCloudSession(existingSession)
269
+ const created = await createRemoteSim({
270
+ bundlePath: input.bundlePath,
271
+ device: selectedDevice,
272
+ authorization: header,
273
+ accountId,
274
+ platform,
275
+ reuse: input.reuse,
276
+ })
277
+ try {
278
+ publishCloudSessionToShell(created.session, descriptor)
279
+ } catch (error) {
280
+ await closeCloudSession(created.session)
281
+ throw error
282
+ }
283
+ const receipt = created.receipt
284
+ const produced = created.artifact
285
+ console.log(` remote simulator: ${receipt.simId}${created.reused ? ' (reused)' : ''}`)
286
+ console.log(` platform: ${platform}`)
287
+ console.log(` device: ${selectedDevice}`)
288
+ console.log(` artifact: ${receipt.artifact.id} (${receipt.artifact.bytes} bytes)`)
289
+ console.log(
290
+ ` source bundle: sha256:${produced.source.sha256} (${produced.source.bytes} bytes)`,
291
+ )
292
+ console.log(
293
+ ` normalized modules: ${produced.modules}; javascript ${produced.javascript.sourceBytes} -> ${produced.javascript.minifiedBytes} bytes`,
294
+ )
295
+ console.log(
296
+ ` embedded assets: ${produced.assets.descriptors} descriptors, ${produced.assets.variants} scales, ${produced.assets.bytes} bytes (${produced.assets.encodedBytes} encoded)`,
297
+ )
298
+ // the dest directory was chosen by resolving this bundle's descriptors
299
+ // against it, so say which one won rather than leaving it invisible.
300
+ if (created.assetsDirectory) {
301
+ console.log(` asset dest: ${created.assetsDirectory}`)
302
+ }
303
+ console.log(` claim expires: ${receipt.claim.expiresAt}`)
304
+ console.log(REMOTE_COMMANDS_HELP)
305
+ for (const warning of created.warnings) console.warn(` warning: ${warning}`)
306
+ if (!created.session.streamUrl) {
307
+ console.log(' the service minted no watch url for this simulator')
308
+ return 0
309
+ }
310
+ return openInBrowser(created.session.streamUrl)
311
+ }
312
+
313
+ /** `rnx remote list`: the simulators this account runs that `rnx remote` made. */
314
+ export async function runRemoteList(args: string[]): Promise<number> {
315
+ try {
316
+ let json = false
317
+ for (const arg of args) {
318
+ if (arg === '--json') {
319
+ json = true
320
+ continue
321
+ }
322
+ throw new Error(`usage: rnx remote list [--json]`)
323
+ }
324
+ const [{ authHeaderOrExit, cloudAccountIdOrExit }, { listRemoteSims }] =
325
+ await Promise.all([import('../auth'), import('../cloud-client')])
326
+ const { auth, header } = authHeaderOrExit('remote list')
327
+ const accountId = cloudAccountIdOrExit(auth)
328
+ const sims = await listRemoteSims({ authorization: header, accountId })
329
+ if (json) {
330
+ console.log(JSON.stringify({ sims }))
331
+ return 0
332
+ }
333
+ if (sims.length === 0) {
334
+ console.log(' no remote simulators are running')
335
+ return 0
336
+ }
337
+ for (const sim of sims) {
338
+ const platformLabel = sim.labels['rnx.platform'] ?? 'unknown'
339
+ console.log(
340
+ ` ${sim.simId} ${sim.status} ${platformLabel} sha256:${sim.artifact.sha256.slice(0, 12)}`,
341
+ )
342
+ }
343
+ return 0
344
+ } catch (error) {
345
+ rethrowIfExit(error)
346
+ console.error(
347
+ ` ${rnxPublicBrand.commandName} remote list failed: ${
348
+ error instanceof Error ? error.message : String(error)
349
+ }`,
350
+ )
351
+ return 1
352
+ }
353
+ }
354
+
355
+ /** `rnx remote stop <id>`: delete one remote simulator by id. */
356
+ export async function runRemoteStop(args: string[]): Promise<number> {
357
+ try {
358
+ const positional = args.filter((arg) => !arg.startsWith('-'))
359
+ if (
360
+ args.some((arg) => arg.startsWith('-')) ||
361
+ positional.length !== 1 ||
362
+ !positional[0]
363
+ ) {
364
+ throw new Error(`usage: rnx remote stop <sim-id>`)
365
+ }
366
+ const simId = positional[0]
367
+ const [
368
+ { authHeaderOrExit, cloudAccountIdOrExit },
369
+ { deleteRemoteSim },
370
+ { publishCloudSessionToShell, readCloudSession, requireCloudSessionShellUpdate },
371
+ ] = await Promise.all([
372
+ import('../auth'),
373
+ import('../cloud-client'),
374
+ import('../cloud-session'),
375
+ ])
376
+ let current: CloudSession | null = null
377
+ try {
378
+ current = readCloudSession()
379
+ } catch {
380
+ current = null
381
+ }
382
+ // stopping the shell's own simulator clears the shell with it, so the
383
+ // update channel is required before anything is deleted.
384
+ const descriptor = current?.simId === simId ? requireCloudSessionShellUpdate() : null
385
+ const { auth, header } = authHeaderOrExit('remote stop')
386
+ const accountId = cloudAccountIdOrExit(auth)
387
+ const deleted = await deleteRemoteSim({
388
+ simId,
389
+ authorization: header,
390
+ accountId,
391
+ })
392
+ if (!deleted) {
393
+ console.error(
394
+ ` ${rnxPublicBrand.commandName} remote stop: ${simId} does not exist`,
395
+ )
396
+ return 1
397
+ }
398
+ if (descriptor !== null) publishCloudSessionToShell(null, descriptor)
399
+ console.log(` stopped remote simulator: ${deleted.simId}`)
400
+ return 0
401
+ } catch (error) {
402
+ rethrowIfExit(error)
403
+ console.error(
404
+ ` ${rnxPublicBrand.commandName} remote stop failed: ${
405
+ error instanceof Error ? error.message : String(error)
406
+ }`,
407
+ )
408
+ return 1
409
+ }
410
+ }