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,348 @@
1
+ // rnx runtime — manage engine runtimes under ~/.rnx/runtimes/.
2
+ //
3
+ // runtimes are versioned tarballs hosted behind the configured CDN origin
4
+ // (default https://contrast.dev/runtimes/). each install:
5
+ // 1. fetches manifest.json to resolve a version (or confirm the literal)
6
+ // 2. downloads sootsim-runtime-<version>.tar.gz to an operation temp directory
7
+ // 3. verifies sha256
8
+ // 4. extracts to ~/.rnx/runtimes/<version>/
9
+ // 5. (optionally) sets it active + tells the daemon to hot-swap
10
+ //
11
+ // the daemon-side state (active runtime) is mirrored in ~/.rnx/runtimes/active
12
+ // so the CLI can change it even when the daemon isn't running.
13
+
14
+ import fs from 'fs'
15
+ import { compareSemver } from '@contrast/runtime-delivery'
16
+ import { WebSocket } from 'ws'
17
+ import {
18
+ ensureRnxHome,
19
+ readActiveRuntime,
20
+ readLiveRuntimeVersions,
21
+ runtimeDir,
22
+ writeActiveRuntime,
23
+ } from '../../src/home-paths'
24
+ import {
25
+ DEFAULT_RUNTIME_CDN_ORIGIN,
26
+ RNX_CHANGELOG_URL,
27
+ rnxRuntime,
28
+ } from '../../src/runtime-delivery'
29
+ import { resolveRNXAppConfig } from '../app-config'
30
+ import { rnxExit } from '../run-rnx'
31
+ import { fetchRuntimeReleaseNotes, formatRuntimeReleaseNotes } from '../runtime-notes'
32
+ import { resolveDefaultBridgePort } from '../ws-bridge'
33
+
34
+ interface RuntimeOptions {
35
+ channel?: string
36
+ }
37
+
38
+ export async function runRuntime(args: string[], opts: RuntimeOptions = {}) {
39
+ const [sub, ...rest] = args
40
+ if (!sub || sub === '--help' || sub === '-h') {
41
+ printHelp()
42
+ return
43
+ }
44
+ switch (sub) {
45
+ case 'install':
46
+ return runtimeInstall(rest, opts)
47
+ case 'list':
48
+ return runtimeList(rest)
49
+ case 'use':
50
+ return runtimeUse(rest)
51
+ case 'remove':
52
+ return runtimeRemove(rest)
53
+ case 'which':
54
+ return runtimeWhich()
55
+ case 'notes':
56
+ return runtimeNotes(rest)
57
+ default:
58
+ console.error(` unknown runtime subcommand: ${sub}`)
59
+ printHelp()
60
+ rnxExit(1)
61
+ }
62
+ }
63
+
64
+ function printHelp() {
65
+ console.log(`
66
+ rnx runtime — manage engine runtimes under ~/.rnx/runtimes/
67
+
68
+ usage:
69
+ rnx runtime install [version] install a version (default: channel latest)
70
+ rnx runtime list show installed + available versions
71
+ rnx runtime use <version> switch active runtime
72
+ rnx runtime remove <version> delete an installed runtime
73
+ rnx runtime which print active runtime version
74
+ rnx runtime notes print repo or default engine release notes
75
+
76
+ flags:
77
+ --channel <name> channel to resolve 'latest' from (default: stable)
78
+ --force reinstall even if the version is already on disk
79
+ --set-active=false do not switch active runtime after install
80
+
81
+ environment:
82
+ RNX_CDN_ORIGIN override the CDN base URL (default: ${DEFAULT_RUNTIME_CDN_ORIGIN})
83
+
84
+ examples:
85
+ rnx runtime install
86
+ rnx runtime install 1.2.3
87
+ rnx runtime install --channel beta
88
+ rnx runtime use 1.2.3
89
+ rnx runtime notes
90
+ `)
91
+ }
92
+
93
+ // --- install --------------------------------------------------------------
94
+
95
+ async function runtimeInstall(args: string[], opts: RuntimeOptions) {
96
+ const { version: versionArg, flags } = parseVersionAndFlags(args)
97
+ const channel = flags.channel ?? opts.channel ?? 'stable'
98
+ const force = flags.force === true
99
+ const setActive = flags.setActive !== false
100
+
101
+ ensureRnxHome()
102
+
103
+ console.log(`rnx runtime install`)
104
+ console.log(` cdn: ${rnxRuntime.resolveCdnOrigin()}`)
105
+
106
+ try {
107
+ const result = await rnxRuntime.install({
108
+ version: versionArg,
109
+ channel,
110
+ force,
111
+ setActive,
112
+ protectVersions: readLiveRuntimeVersions(),
113
+ })
114
+ console.log(` version: ${result.version} (channel: ${result.channel})`)
115
+ if (result.installed) {
116
+ console.log(` installed ${result.version}`)
117
+ } else {
118
+ console.log(` already installed at ${result.runtimeDir}`)
119
+ }
120
+ if (setActive) await activateVersion(result.version)
121
+ return result
122
+ } catch (err) {
123
+ console.error(` ${describeError(err)}`)
124
+ rnxExit(1)
125
+ }
126
+ }
127
+
128
+ async function activateVersion(version: string) {
129
+ writeActiveRuntime(version)
130
+ console.log(` active: ${version}`)
131
+ // if a daemon is running, tell it to hot-swap its http root. the
132
+ // daemon re-reads the active runtime per-request anyway, so this is
133
+ // mostly a nudge — but it also broadcasts runtime:changed so any
134
+ // connected electron webContents can reload. silent failure is fine;
135
+ // the next daemon start picks up the active file from disk.
136
+ const notified = await tellDaemonRuntimeChanged(version)
137
+ if (!notified) {
138
+ console.log(
139
+ ` (no daemon running — next rnx/electron launch will pick up ${version})`,
140
+ )
141
+ }
142
+ }
143
+
144
+ /** notify a running daemon that the active runtime changed so it hot-swaps
145
+ * its http root + pushes a runtime:changed message to every connected
146
+ * browser (electron). uses a raw WebSocket to skip the bridge's
147
+ * primary-browser resolution — this message is daemon-only, not routed
148
+ * to a browser. returns true when the daemon acknowledged; false when
149
+ * no fresh daemon was reachable (caller logs accordingly).
150
+ *
151
+ * skips the ws probe entirely when the lockfile isn't fresh — the
152
+ * default-port fallback in resolveDefaultBridgePort() would otherwise
153
+ * eat a full handshakeTimeout on every `runtime use` when no daemon
154
+ * is running. */
155
+ async function tellDaemonRuntimeChanged(version: string): Promise<boolean> {
156
+ const { isDaemonLockfileFresh, readDaemonLockfile } =
157
+ await import('../../src/home-paths')
158
+ const lock = readDaemonLockfile()
159
+ if (!isDaemonLockfileFresh(lock)) return false
160
+ const port = resolveDefaultBridgePort()
161
+ return new Promise<boolean>((resolve) => {
162
+ let settled = false
163
+ let success = false
164
+ const finish = (ok: boolean) => {
165
+ if (settled) return
166
+ settled = true
167
+ resolve(ok)
168
+ }
169
+ const ws = new WebSocket(`ws://127.0.0.1:${port}`, {
170
+ handshakeTimeout: 800,
171
+ })
172
+ const timer = setTimeout(() => {
173
+ try {
174
+ ws.close()
175
+ } catch {}
176
+ finish(success)
177
+ }, 1500)
178
+ ws.on('open', () => {
179
+ try {
180
+ ws.send(JSON.stringify({ type: 'runtime:use', version, id: 0 }))
181
+ success = true
182
+ } catch {}
183
+ // give the daemon a beat to process, then close
184
+ setTimeout(() => {
185
+ try {
186
+ ws.close()
187
+ } catch {}
188
+ }, 100)
189
+ })
190
+ ws.on('close', () => {
191
+ clearTimeout(timer)
192
+ finish(success)
193
+ })
194
+ ws.on('error', () => {
195
+ clearTimeout(timer)
196
+ finish(false)
197
+ })
198
+ })
199
+ }
200
+
201
+ // --- list -----------------------------------------------------------------
202
+
203
+ async function runtimeList(_args: string[]) {
204
+ ensureRnxHome()
205
+ const installed = rnxRuntime.listInstalled()
206
+ const active = readActiveRuntime()
207
+ console.log(`installed:`)
208
+ if (installed.length === 0) {
209
+ console.log(` (none)`)
210
+ } else {
211
+ for (const v of installed) {
212
+ const marker = v === active ? '*' : ' '
213
+ console.log(` ${marker} ${v}`)
214
+ }
215
+ }
216
+ try {
217
+ const manifest = await rnxRuntime.fetchManifest()
218
+ console.log(`available (latest per channel):`)
219
+ for (const [name, ch] of Object.entries(manifest.channels)) {
220
+ console.log(` ${name.padEnd(8)} ${ch.latest}`)
221
+ }
222
+ const hosted = Object.keys(manifest.versions).sort(compareSemver).reverse()
223
+ if (hosted.length > 1) {
224
+ console.log(`hosted versions:`)
225
+ for (const v of hosted) console.log(` ${v}`)
226
+ }
227
+ } catch (err) {
228
+ console.log(`available: (could not fetch manifest: ${describeError(err)})`)
229
+ }
230
+ }
231
+
232
+ // --- use ------------------------------------------------------------------
233
+
234
+ async function runtimeUse(args: string[]) {
235
+ const version = args[0]
236
+ if (!version) {
237
+ console.error(` usage: rnx runtime use <version>`)
238
+ rnxExit(1)
239
+ }
240
+ const installed = rnxRuntime.listInstalled()
241
+ if (!installed.includes(version)) {
242
+ console.error(` version ${version} is not installed`)
243
+ console.error(` installed: ${installed.join(', ') || '(none)'}`)
244
+ console.error(` run \`rnx runtime install ${version}\` first`)
245
+ rnxExit(1)
246
+ }
247
+ await activateVersion(version)
248
+ }
249
+
250
+ // --- remove ---------------------------------------------------------------
251
+
252
+ async function runtimeRemove(args: string[]) {
253
+ const version = args[0]
254
+ if (!version) {
255
+ console.error(` usage: rnx runtime remove <version>`)
256
+ rnxExit(1)
257
+ }
258
+ const active = readActiveRuntime()
259
+ if (active === version) {
260
+ console.error(` cannot remove active runtime ${version}`)
261
+ console.error(
262
+ ` switch with \`rnx runtime use <other>\` first, or install another version`,
263
+ )
264
+ rnxExit(1)
265
+ }
266
+ if (readLiveRuntimeVersions().includes(version)) {
267
+ console.error(` cannot remove runtime ${version} while a project is using it`)
268
+ rnxExit(1)
269
+ }
270
+ const dir = runtimeDir(version)
271
+ if (!fs.existsSync(dir)) {
272
+ console.error(` ${version} is not installed`)
273
+ return
274
+ }
275
+ fs.rmSync(dir, { recursive: true, force: true })
276
+ console.log(` removed ${version}`)
277
+ }
278
+
279
+ // --- which ----------------------------------------------------------------
280
+
281
+ async function runtimeWhich() {
282
+ const active = readActiveRuntime()
283
+ if (!active) {
284
+ console.log(` no active runtime`)
285
+ return
286
+ }
287
+ console.log(active)
288
+ }
289
+
290
+ // --- notes ----------------------------------------------------------------
291
+
292
+ async function runtimeNotes(args: string[]) {
293
+ if (args.length > 0) {
294
+ console.error(` usage: rnx runtime notes`)
295
+ rnxExit(1)
296
+ }
297
+ const { config } = await resolveRNXAppConfig()
298
+ const version = config?.runtimeVersion ?? readActiveRuntime()
299
+ if (!version) {
300
+ console.log(` no active runtime`)
301
+ console.log(` release notes: ${RNX_CHANGELOG_URL}`)
302
+ return
303
+ }
304
+ console.log(formatRuntimeReleaseNotes(version, await fetchRuntimeReleaseNotes(version)))
305
+ }
306
+
307
+ // --- helpers --------------------------------------------------------------
308
+
309
+ interface InstallFlags {
310
+ channel?: string
311
+ force?: boolean
312
+ setActive?: boolean
313
+ }
314
+
315
+ function parseVersionAndFlags(args: string[]): {
316
+ version: string | null
317
+ flags: InstallFlags
318
+ } {
319
+ const flags: InstallFlags = {}
320
+ const positional: string[] = []
321
+ for (let i = 0; i < args.length; i++) {
322
+ const arg = args[i]
323
+ if (arg === '--channel' && i + 1 < args.length) {
324
+ flags.channel = args[i + 1]
325
+ i++
326
+ continue
327
+ }
328
+ if (arg.startsWith('--channel=')) {
329
+ flags.channel = arg.slice('--channel='.length)
330
+ continue
331
+ }
332
+ if (arg === '--force') {
333
+ flags.force = true
334
+ continue
335
+ }
336
+ if (arg === '--set-active=false' || arg === '--no-set-active') {
337
+ flags.setActive = false
338
+ continue
339
+ }
340
+ positional.push(arg)
341
+ }
342
+ return { version: positional[0] ?? null, flags }
343
+ }
344
+
345
+ function describeError(err: unknown): string {
346
+ if (err instanceof Error) return err.message
347
+ return String(err)
348
+ }
@@ -0,0 +1,106 @@
1
+ import { runScreenshot } from './screenshot'
2
+ import { runScreenshots } from './screenshots'
3
+ import { runSlides } from './slides'
4
+
5
+ interface ScreenshotCommandOptions {
6
+ port?: number
7
+ verbose?: boolean
8
+ }
9
+
10
+ const VALUE_FLAGS = new Set([
11
+ '--sim',
12
+ '--port',
13
+ '-p',
14
+ '--output',
15
+ '-o',
16
+ '--out',
17
+ '--plan',
18
+ '--app',
19
+ '--device',
20
+ '--org',
21
+ '--repo',
22
+ '--api-origin',
23
+ '--branch',
24
+ '--platform',
25
+ '--slides',
26
+ '--shell-origin',
27
+ '--timeout',
28
+ '--ids',
29
+ '--out-dir',
30
+ ])
31
+ const SUBCOMMANDS = new Set(['capture', 'slides', 'appstore', 'layers'])
32
+
33
+ function findSubcommand(args: string[]): { name: string; index: number } | null {
34
+ for (let i = 0; i < args.length; i++) {
35
+ const arg = args[i]
36
+ if (arg.startsWith('-')) {
37
+ if (VALUE_FLAGS.has(arg)) i++
38
+ continue
39
+ }
40
+ return SUBCOMMANDS.has(arg) ? { name: arg, index: i } : null
41
+ }
42
+ return null
43
+ }
44
+
45
+ function removeAt(args: string[], index: number): string[] {
46
+ return [...args.slice(0, index), ...args.slice(index + 1)]
47
+ }
48
+
49
+ function stripSimFlag(args: string[]): string[] {
50
+ const out: string[] = []
51
+ for (let i = 0; i < args.length; i++) {
52
+ if (args[i] === '--sim') {
53
+ i++
54
+ continue
55
+ }
56
+ out.push(args[i])
57
+ }
58
+ return out
59
+ }
60
+
61
+ export async function runScreenshotCommand(
62
+ args: string[],
63
+ opts: ScreenshotCommandOptions,
64
+ ): Promise<number | void> {
65
+ const sub = findSubcommand(args)
66
+ if (!sub || sub.name === 'capture') {
67
+ await runScreenshot(sub ? removeAt(args, sub.index) : args, opts)
68
+ return
69
+ }
70
+
71
+ if (sub.name === 'slides') {
72
+ await runSlides(removeAt(args, sub.index), opts)
73
+ return
74
+ }
75
+
76
+ if (sub.name === 'layers') {
77
+ const { runScreenshotLayers } = await import('./screenshot-layers')
78
+ await runScreenshotLayers(removeAt(args, sub.index), opts)
79
+ return
80
+ }
81
+
82
+ const appstoreArgs = removeAt(args, sub.index)
83
+ const nested = findSubcommand(appstoreArgs)
84
+ if (nested?.name === 'capture') {
85
+ // repo-only tooling: screenshots-capture statically imports playwright-core,
86
+ // which the published CLI never ships (it's external in build-cli.ts). a
87
+ // static import here would put that bare import at the top of THIS chunk,
88
+ // so every published `rnx screenshot` printed ERR_MODULE_NOT_FOUND —
89
+ // keep it a runtime dynamic import that only evaluates when capture runs.
90
+ try {
91
+ const { runScreenshotsCapture } = await import('./screenshots-capture')
92
+ return await runScreenshotsCapture(
93
+ stripSimFlag(removeAt(appstoreArgs, nested.index)),
94
+ )
95
+ } catch (err) {
96
+ if (err instanceof Error && 'code' in err && err.code === 'ERR_MODULE_NOT_FOUND') {
97
+ console.error(
98
+ 'screenshot appstore capture is repo-only tooling — it needs the rnx workspace (playwright is not shipped with the published CLI).',
99
+ )
100
+ return 1
101
+ }
102
+ throw err
103
+ }
104
+ }
105
+ return runScreenshots(appstoreArgs, opts)
106
+ }
@@ -0,0 +1,144 @@
1
+ // rnx screenshot layers — engine-rendered layer isolation export.
2
+ //
3
+ // captures one screen as a stack the 3d stage can pull apart: `full.png` is
4
+ // the normal frame, `base.png` re-renders the frame with the target subtrees
5
+ // hidden (content and glass blur behind them are real, not inpainted), and
6
+ // each `layer-<testID>.png` draws exactly that subtree alone on transparency
7
+ // at its on-screen position. `manifest.json` records device metrics and the
8
+ // logical rect of every layer.
9
+
10
+ import { mkdirSync, writeFileSync } from 'fs'
11
+ import { resolve } from 'path'
12
+ import { devices } from 'sootsim-engine/settings'
13
+ import { buildResolveRectEval, type SampleRect } from '../browser-evals'
14
+ import { rnxExit } from '../run-rnx'
15
+ import { callInBridge, createBridgeFromParsed, parseBridgeCliArgs } from '../ws-bridge'
16
+ import { inspectWaitReady, waitReadyReason } from './inspect/core'
17
+
18
+ interface ScreenshotLayersOptions {
19
+ port?: number
20
+ verbose?: boolean
21
+ }
22
+
23
+ export async function runScreenshotLayers(
24
+ args: string[],
25
+ opts: ScreenshotLayersOptions,
26
+ ): Promise<void> {
27
+ if (args.includes('--help') || args.includes('-h')) {
28
+ console.log(`
29
+ rnx screenshot layers — export a screen as engine-isolated layers
30
+
31
+ usage:
32
+ rnx screenshot layers --ids <testID,testID,...> [--out-dir <dir>]
33
+
34
+ options:
35
+ --ids <list> comma-separated testIDs of the subtrees to isolate
36
+ --out-dir <dir> output directory (default: /tmp/rnx-layers)
37
+
38
+ output:
39
+ full.png the normal frame
40
+ base.png the frame re-rendered with every listed subtree hidden
41
+ layer-<id>.png each subtree rendered alone on a transparent frame
42
+ manifest.json device metrics + logical rect per layer
43
+ `)
44
+ rnxExit(0)
45
+ }
46
+
47
+ const idsArg = args.find((_, i) => args[i - 1] === '--ids')
48
+ if (!idsArg) {
49
+ console.error(' --ids is required (comma-separated testIDs)')
50
+ rnxExit(1)
51
+ }
52
+ const testIds = idsArg
53
+ .split(',')
54
+ .map((entry) => entry.trim())
55
+ .filter(Boolean)
56
+ if (testIds.length === 0) {
57
+ console.error(' --ids resolved to an empty list')
58
+ rnxExit(1)
59
+ }
60
+ const outDirArg = args.find((_, i) => args[i - 1] === '--out-dir')
61
+ const outDir = resolve(process.cwd(), outDirArg ?? '/tmp/rnx-layers')
62
+
63
+ const parsed = parseBridgeCliArgs(args, {
64
+ port: opts.port,
65
+ stripValueFlags: ['--ids', '--out-dir'],
66
+ })
67
+ const bridge = createBridgeFromParsed({ ...parsed, commandTimeoutMs: 45000 })
68
+ try {
69
+ const readiness = await inspectWaitReady(bridge, 90_000)
70
+ if (!readiness.ready) {
71
+ throw new Error(
72
+ `app not ready to capture after ${Math.round(readiness.elapsedMs / 1000)}s ` +
73
+ `(${waitReadyReason(readiness)})`,
74
+ )
75
+ }
76
+
77
+ const rects: Record<string, SampleRect> = {}
78
+ for (const testId of testIds) {
79
+ const node = await bridge.send({
80
+ type: 'evaluate',
81
+ code: buildResolveRectEval({ id: testId }),
82
+ })
83
+ if (!node) throw new Error(`no node with testID "${testId}"`)
84
+ rects[testId] = node as SampleRect
85
+ }
86
+
87
+ mkdirSync(outDir, { recursive: true })
88
+ const savePng = (name: string, dataUrl: unknown) => {
89
+ if (typeof dataUrl !== 'string' || !dataUrl.startsWith('data:image/png;base64,')) {
90
+ throw new Error(`capture for ${name} returned no image data`)
91
+ }
92
+ const path = resolve(outDir, name)
93
+ writeFileSync(
94
+ path,
95
+ Buffer.from(dataUrl.slice('data:image/png;base64,'.length), 'base64'),
96
+ )
97
+ console.log(` saved: ${path}`)
98
+ }
99
+
100
+ savePng('full.png', await bridge.send({ type: 'screenshot' }))
101
+ savePng(
102
+ 'base.png',
103
+ await bridge.send({
104
+ type: 'screenshot',
105
+ captureFilter: { hideTestIds: testIds },
106
+ }),
107
+ )
108
+ for (const testId of testIds) {
109
+ // every other listed target hides inside this solo, so nested targets
110
+ // (a button inside a lifted card) land in exactly one layer.
111
+ savePng(
112
+ `layer-${testId}.png`,
113
+ await bridge.send({
114
+ type: 'screenshot',
115
+ captureFilter: {
116
+ soloTestId: testId,
117
+ hideTestIds: testIds.filter((other) => other !== testId),
118
+ },
119
+ }),
120
+ )
121
+ }
122
+
123
+ const settings = (await callInBridge<Record<string, unknown> | null>(
124
+ bridge,
125
+ 'SootSim.bridges.settings.get',
126
+ )) ?? { deviceModel: null }
127
+ const model =
128
+ typeof settings.deviceModel === 'string' && settings.deviceModel in devices
129
+ ? settings.deviceModel
130
+ : null
131
+ const device = model ? devices[model as keyof typeof devices] : null
132
+ const manifest = {
133
+ device: device
134
+ ? { model, width: device.width, height: device.height, scale: device.scale }
135
+ : { model },
136
+ layers: testIds.map((testId) => ({ testId, rect: rects[testId] })),
137
+ }
138
+ const manifestPath = resolve(outDir, 'manifest.json')
139
+ writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`)
140
+ console.log(` saved: ${manifestPath}`)
141
+ } finally {
142
+ bridge.close()
143
+ }
144
+ }
@@ -0,0 +1,38 @@
1
+ // rnx mode screenshot — toggle the "screenshot mode" chrome overlay
2
+ // in the running rnx sim. orthogonal to `--no-shell` on the
3
+ // `screenshot` command: this hides the DOM browser chrome (rail, menubar,
4
+ // window controls) while still rendering the iOS shell inside the canvas.
5
+ //
6
+ // wraps the existing `toggle-screenshot-mode` shell action so the CLI
7
+ // stays behavior-compatible with the menu item / keyboard shortcut.
8
+
9
+ import { rnxExit } from '../run-rnx'
10
+ import { runShellBooleanMode, type ShellBooleanModeOptions } from './shell-boolean-mode'
11
+
12
+ export async function runScreenshotMode(args: string[], opts: ShellBooleanModeOptions) {
13
+ if (args.includes('--help') || args.includes('-h')) {
14
+ console.log(`
15
+ rnx mode screenshot — toggle the screenshot-mode chrome overlay
16
+
17
+ usage:
18
+ rnx mode screenshot [on|off|toggle]
19
+
20
+ arguments:
21
+ on enable screenshot mode (enables showFrame if needed)
22
+ off disable screenshot mode
23
+ toggle flip the current state (default)
24
+
25
+ examples:
26
+ rnx mode screenshot
27
+ rnx mode screenshot on
28
+ rnx mode screenshot off
29
+ `)
30
+ rnxExit(0)
31
+ }
32
+
33
+ await runShellBooleanMode(args, opts, {
34
+ modeKey: 'screenshotMode',
35
+ displayName: 'screenshot-mode',
36
+ actionId: 'toggle-screenshot-mode',
37
+ })
38
+ }