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,44 @@
1
+ import type {
2
+ RnxBrowserOperations,
3
+ RnxCommandBackend,
4
+ RnxCommandInvocation,
5
+ RnxExecutionContext,
6
+ } from './command-contract'
7
+
8
+ export function createRnxBrowserBackend(
9
+ operations: RnxBrowserOperations,
10
+ ): RnxCommandBackend {
11
+ return {
12
+ kind: 'browser',
13
+ execute(invocation: RnxCommandInvocation, context: RnxExecutionContext) {
14
+ switch (invocation.family) {
15
+ case 'preview':
16
+ return operations.selectPreview(invocation, context)
17
+ case 'runtime':
18
+ return operations.readRuntime(invocation, context)
19
+ case 'tree':
20
+ return operations.inspectTree(invocation, context)
21
+ case 'layout':
22
+ return operations.inspectLayout(invocation, context)
23
+ case 'find':
24
+ return operations.find(invocation, context)
25
+ case 'wait':
26
+ return operations.wait(invocation, context)
27
+ case 'action':
28
+ return operations.act(invocation, context)
29
+ case 'screenshot':
30
+ return operations.screenshot(invocation, context)
31
+ case 'logs':
32
+ return operations.readLogs(invocation, context)
33
+ case 'network':
34
+ return operations.readNetwork(invocation, context)
35
+ case 'storage':
36
+ return operations.accessStorage(invocation, context)
37
+ case 'history':
38
+ return operations.readHistory(invocation, context)
39
+ case 'evidence':
40
+ return operations.collectEvidence(invocation, context)
41
+ }
42
+ },
43
+ }
44
+ }
@@ -0,0 +1,96 @@
1
+ // browser-side evaluator snippets for CLI inspect commands.
2
+ //
3
+ // the rnx CLI ships some commands that need to observe the live
4
+ // rnx tree or its rendered pixels. we send these strings over the rnx host's
5
+ // `evaluate` handler.
6
+ //
7
+ // cropping happens once, in the capture module (`bridges.screenshot`),
8
+ // via its `crop` option. these evals intentionally defer to that bridge
9
+ // instead of reimplementing the dpr-aware crop math.
10
+
11
+ export interface SampleRect {
12
+ x: number
13
+ y: number
14
+ w: number
15
+ h: number
16
+ }
17
+
18
+ // resolve a --id/--text matcher into a SampleRect using __sootsimTest.
19
+ // returns null on miss so the caller can report a clear not-found error.
20
+ export function buildResolveRectEval(matcher: { id?: string; text?: string }): string {
21
+ return `(async () => {
22
+ const __t = window.__sootsimTest
23
+ if (!__t) throw new Error('__sootsimTest not available')
24
+ const __id = ${JSON.stringify(matcher.id || '')}
25
+ const __text = ${JSON.stringify(matcher.text || '')}
26
+ let __node = null
27
+ if (__id) {
28
+ __node = (__t.findByTestId && await __t.findByTestId(__id)) ||
29
+ (__t.findById && await __t.findById(__id)) || null
30
+ } else if (__text) {
31
+ __node = __t.findByText ? await __t.findByText(__text) : null
32
+ }
33
+ if (!__node) return null
34
+ const __pos = __node.absolutePosition || { x: __node.layout?.x || 0, y: __node.layout?.y || 0 }
35
+ const __layout = __node.layout || {}
36
+ return {
37
+ x: __pos.x,
38
+ y: __pos.y,
39
+ w: __layout.width || 1,
40
+ h: __layout.height || 1,
41
+ }
42
+ })()`
43
+ }
44
+
45
+ // sample-color averages rgba pixels across a logical-coord rect. the eval
46
+ // asks the capture bridge for an already-cropped PNG and then averages the
47
+ // decoded ImageData in one pass.
48
+ export function buildSampleColorEval(rect: SampleRect): string {
49
+ return `(async () => {
50
+ const __rect = ${JSON.stringify(rect)}
51
+ const __screenshot = window.__sootsimScreenshot
52
+ if (typeof __screenshot !== 'function') {
53
+ throw new Error('screenshot bridge not installed')
54
+ }
55
+ const __dataUrl = await __screenshot({ crop: __rect })
56
+ const __img = await new Promise((res, rej) => {
57
+ const img = new Image()
58
+ img.onload = () => res(img)
59
+ img.onerror = () => rej(new Error('failed to decode cropped screenshot'))
60
+ img.src = __dataUrl
61
+ })
62
+ const __w = __img.naturalWidth
63
+ const __h = __img.naturalHeight
64
+ const __canvas = document.createElement('canvas')
65
+ __canvas.width = __w
66
+ __canvas.height = __h
67
+ const __ctx = __canvas.getContext('2d')
68
+ if (!__ctx) throw new Error('could not acquire 2d context')
69
+ __ctx.drawImage(__img, 0, 0)
70
+ const __data = __ctx.getImageData(0, 0, __w, __h).data
71
+ const __n = __data.length / 4
72
+ let __r = 0, __g = 0, __b = 0, __a = 0
73
+ for (let __i = 0; __i < __data.length; __i += 4) {
74
+ __r += __data[__i]
75
+ __g += __data[__i + 1]
76
+ __b += __data[__i + 2]
77
+ __a += __data[__i + 3]
78
+ }
79
+ const __round = (v) => Math.round(v / __n)
80
+ const __rr = __round(__r), __gg = __round(__g), __bb = __round(__b), __aa = __round(__a)
81
+ const __hex =
82
+ '#' +
83
+ [__rr, __gg, __bb]
84
+ .map((v) => v.toString(16).padStart(2, '0'))
85
+ .join('')
86
+ const __dpr = window.devicePixelRatio || 1
87
+ return {
88
+ r: __rr, g: __gg, b: __bb, a: __aa,
89
+ hex: __hex,
90
+ samples: __n,
91
+ rect: { x: __rect.x, y: __rect.y, w: __rect.w, h: __rect.h },
92
+ deviceWidth: __w / __dpr,
93
+ deviceHeight: __h / __dpr,
94
+ }
95
+ })()`
96
+ }
@@ -0,0 +1,261 @@
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
3
+ import { compareSemver } from '@contrast/runtime-delivery'
4
+ import { getCliVersion } from '../src/cli-version'
5
+ import { cliUpdateFilePath } from '../src/home-paths'
6
+ import { isAgentEnv } from './commands/inspect/env'
7
+
8
+ export const DEFAULT_CLI_RELEASE_ORIGIN = 'https://r2.rnxsim.com/rnx-cli'
9
+ export const CLI_UPDATE_CHECK_INTERVAL_MS = 20 * 60 * 60 * 1000
10
+ export const CLI_UPDATE_NOTIFICATION_COOLDOWN_MS = 7 * 24 * 60 * 60 * 1000
11
+
12
+ const CLI_UPDATE_STATE_SCHEMA = 1 as const
13
+ const CLI_VERSION_PATTERN = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/
14
+ let stateWriteSequence = 0
15
+
16
+ interface CliUpdateState {
17
+ schema: typeof CLI_UPDATE_STATE_SCHEMA
18
+ latestVersion: string | null
19
+ checkedAt: number
20
+ lastAttemptAt: number
21
+ lastNotifiedVersion: string | null
22
+ lastNotifiedAt: number
23
+ }
24
+
25
+ export interface CliUpdateAvailability {
26
+ currentVersion: string
27
+ latestVersion: string
28
+ outdated: boolean
29
+ }
30
+
31
+ export interface CliUpdateInvocation {
32
+ command: string | null
33
+ commandArgs: readonly string[]
34
+ exitCode: number
35
+ standalone: boolean
36
+ stderrIsTTY: boolean
37
+ stdoutIsTTY: boolean
38
+ version: boolean
39
+ env?: Readonly<Record<string, string | undefined>>
40
+ }
41
+
42
+ function emptyState(): CliUpdateState {
43
+ return {
44
+ schema: CLI_UPDATE_STATE_SCHEMA,
45
+ latestVersion: null,
46
+ checkedAt: 0,
47
+ lastAttemptAt: 0,
48
+ lastNotifiedVersion: null,
49
+ lastNotifiedAt: 0,
50
+ }
51
+ }
52
+
53
+ function isRecord(value: unknown): value is Record<string, unknown> {
54
+ return value !== null && typeof value === 'object'
55
+ }
56
+
57
+ function isTimestamp(value: unknown): value is number {
58
+ return typeof value === 'number' && Number.isFinite(value) && value >= 0
59
+ }
60
+
61
+ function readState(): CliUpdateState {
62
+ try {
63
+ const parsed: unknown = JSON.parse(fs.readFileSync(cliUpdateFilePath(), 'utf8'))
64
+ if (
65
+ !isRecord(parsed) ||
66
+ parsed.schema !== CLI_UPDATE_STATE_SCHEMA ||
67
+ (parsed.latestVersion !== null &&
68
+ (typeof parsed.latestVersion !== 'string' ||
69
+ !CLI_VERSION_PATTERN.test(parsed.latestVersion))) ||
70
+ !isTimestamp(parsed.checkedAt) ||
71
+ !isTimestamp(parsed.lastAttemptAt) ||
72
+ (parsed.lastNotifiedVersion !== null &&
73
+ (typeof parsed.lastNotifiedVersion !== 'string' ||
74
+ !CLI_VERSION_PATTERN.test(parsed.lastNotifiedVersion))) ||
75
+ !isTimestamp(parsed.lastNotifiedAt)
76
+ ) {
77
+ return emptyState()
78
+ }
79
+ return {
80
+ schema: CLI_UPDATE_STATE_SCHEMA,
81
+ latestVersion: parsed.latestVersion,
82
+ checkedAt: parsed.checkedAt,
83
+ lastAttemptAt: parsed.lastAttemptAt,
84
+ lastNotifiedVersion: parsed.lastNotifiedVersion,
85
+ lastNotifiedAt: parsed.lastNotifiedAt,
86
+ }
87
+ } catch {
88
+ return emptyState()
89
+ }
90
+ }
91
+
92
+ function writeState(state: CliUpdateState): boolean {
93
+ const file = cliUpdateFilePath()
94
+ const temporary = `${file}.tmp-${process.pid}-${stateWriteSequence++}`
95
+ try {
96
+ fs.mkdirSync(path.dirname(file), { recursive: true })
97
+ fs.writeFileSync(temporary, `${JSON.stringify(state)}\n`, 'utf8')
98
+ fs.renameSync(temporary, file)
99
+ return true
100
+ } catch {
101
+ try {
102
+ fs.rmSync(temporary, { force: true })
103
+ } catch {}
104
+ return false
105
+ }
106
+ }
107
+
108
+ function availability(
109
+ state: CliUpdateState,
110
+ currentVersion = getCliVersion(),
111
+ ): CliUpdateAvailability | null {
112
+ if (!state.latestVersion) return null
113
+ return {
114
+ currentVersion,
115
+ latestVersion: state.latestVersion,
116
+ outdated: compareSemver(state.latestVersion, currentVersion) > 0,
117
+ }
118
+ }
119
+
120
+ function envFlagIsOff(value: string | undefined): boolean {
121
+ return (
122
+ value === '0' || value?.toLowerCase() === 'false' || value?.toLowerCase() === 'off'
123
+ )
124
+ }
125
+
126
+ function envFlagIsOn(value: string | undefined): boolean {
127
+ return Boolean(value) && !envFlagIsOff(value)
128
+ }
129
+
130
+ export function automaticCliUpdateChecksEnabled(
131
+ env: Readonly<Record<string, string | undefined>> = process.env,
132
+ ): boolean {
133
+ return !envFlagIsOff(env.RNX_UPDATE_CHECK)
134
+ }
135
+
136
+ export function shouldUseAutomaticCliUpdateCheck(
137
+ invocation: CliUpdateInvocation,
138
+ ): boolean {
139
+ const env = invocation.env ?? process.env
140
+ if (
141
+ !invocation.standalone ||
142
+ invocation.exitCode !== 0 ||
143
+ invocation.version ||
144
+ !invocation.stdoutIsTTY ||
145
+ !invocation.stderrIsTTY ||
146
+ !automaticCliUpdateChecksEnabled(env) ||
147
+ envFlagIsOn(env.CI) ||
148
+ envFlagIsOn(env.GITHUB_ACTIONS) ||
149
+ envFlagIsOn(env.RNX_NO_PROMPT) ||
150
+ isAgentEnv(env) ||
151
+ invocation.commandArgs.includes('--json') ||
152
+ invocation.commandArgs.includes('--ci') ||
153
+ invocation.commandArgs.includes('--dry-run')
154
+ ) {
155
+ return false
156
+ }
157
+
158
+ return !['agent-wrapper', 'cleanup', 'daemon', 'serve', 'upgrade', 'version'].includes(
159
+ invocation.command ?? '',
160
+ )
161
+ }
162
+
163
+ export function claimAutomaticCliUpdateCheck(now = Date.now()): boolean {
164
+ const state = readState()
165
+ const lastCheck = Math.max(state.checkedAt, state.lastAttemptAt)
166
+ if (now >= lastCheck && now - lastCheck < CLI_UPDATE_CHECK_INTERVAL_MS) {
167
+ return false
168
+ }
169
+ return writeState({ ...state, lastAttemptAt: now })
170
+ }
171
+
172
+ export function readCachedCliUpdate(
173
+ currentVersion = getCliVersion(),
174
+ ): CliUpdateAvailability | null {
175
+ return availability(readState(), currentVersion)
176
+ }
177
+
178
+ export function takeCliUpdateNotification(
179
+ currentVersion = getCliVersion(),
180
+ now = Date.now(),
181
+ ): CliUpdateAvailability | null {
182
+ const state = readState()
183
+ const update = availability(state, currentVersion)
184
+ if (!update?.outdated || state.lastNotifiedVersion === update.latestVersion) return null
185
+ if (
186
+ now >= state.lastNotifiedAt &&
187
+ now - state.lastNotifiedAt < CLI_UPDATE_NOTIFICATION_COOLDOWN_MS
188
+ ) {
189
+ return null
190
+ }
191
+ if (
192
+ !writeState({
193
+ ...state,
194
+ lastNotifiedVersion: update.latestVersion,
195
+ lastNotifiedAt: now,
196
+ })
197
+ ) {
198
+ return null
199
+ }
200
+ return update
201
+ }
202
+
203
+ export async function fetchCliReleaseFile(
204
+ url: string,
205
+ timeoutMs: number,
206
+ ): Promise<Buffer> {
207
+ let response: Response
208
+ try {
209
+ response = await fetch(url, {
210
+ headers: { accept: 'application/octet-stream' },
211
+ signal: AbortSignal.timeout(timeoutMs),
212
+ })
213
+ } catch (error) {
214
+ throw new Error(
215
+ `could not reach the rnx update service: ${
216
+ error instanceof Error ? error.message : String(error)
217
+ }`,
218
+ )
219
+ }
220
+ if (!response.ok) {
221
+ throw new Error(
222
+ `rnx update download failed: ${response.status} ${response.statusText}`,
223
+ )
224
+ }
225
+ return Buffer.from(await response.arrayBuffer())
226
+ }
227
+
228
+ export async function refreshCliUpdateCache(
229
+ options: { releaseOrigin?: string; now?: number; timeoutMs?: number } = {},
230
+ ): Promise<CliUpdateAvailability> {
231
+ const releaseOrigin = (options.releaseOrigin ?? DEFAULT_CLI_RELEASE_ORIGIN).replace(
232
+ /\/+$/,
233
+ '',
234
+ )
235
+ const latestVersion = (
236
+ await fetchCliReleaseFile(
237
+ `${releaseOrigin}/channels/stable`,
238
+ options.timeoutMs ?? 8_000,
239
+ )
240
+ )
241
+ .toString('utf8')
242
+ .trim()
243
+ if (!CLI_VERSION_PATTERN.test(latestVersion)) {
244
+ throw new Error(
245
+ `rnx update service returned an invalid version: ${latestVersion || '(empty)'}`,
246
+ )
247
+ }
248
+
249
+ const now = options.now ?? Date.now()
250
+ const state = readState()
251
+ const next: CliUpdateState = {
252
+ ...state,
253
+ latestVersion,
254
+ checkedAt: now,
255
+ lastAttemptAt: now,
256
+ }
257
+ writeState(next)
258
+ const update = availability(next)
259
+ if (!update) throw new Error('rnx update cache did not retain the latest version')
260
+ return update
261
+ }