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,259 @@
1
+ // shared helpers for inspect subcommands.
2
+ //
3
+ // pulled out of the monolithic inspect.ts so each subcommand can live in its
4
+ // own file without reaching back into a 4000-line switch. everything here is
5
+ // call-agnostic: just bridge-facing helpers that any subcommand can use.
6
+
7
+ import {
8
+ callInBridge,
9
+ callInBridgeWrite,
10
+ createBridge,
11
+ type ParsedBridgeCliArgs,
12
+ type WsBridge,
13
+ } from '../../ws-bridge'
14
+ import { getShellState, inspectWaitReady, isShellCommandUnavailable } from './core'
15
+ import type { SootSimScreenTransitionWaitResult } from '@rnx/globals'
16
+
17
+ // shell-state helpers live in the transport-agnostic kernel module so the CLI
18
+ // and the in-browser agent share one implementation. re-exported here so
19
+ // existing subcommand imports (`./shared`) keep working.
20
+ export { getShellState, isShellCommandUnavailable }
21
+
22
+ export const SCREEN_TRANSITION_PREFLIGHT = {
23
+ timeoutMs: 1800,
24
+ settleMs: 48,
25
+ startWindowMs: 64,
26
+ } as const
27
+
28
+ export function sleep(ms: number) {
29
+ return new Promise((resolve) => setTimeout(resolve, ms))
30
+ }
31
+
32
+ // true when the caller passed --json. read verbs use this to route their
33
+ // payload to stdout-as-json and send everything else (headers, hints,
34
+ // warnings) to stderr so `jq` on stdout never sees framing noise.
35
+ export function wantsJson(args: string[]): boolean {
36
+ return args.includes('--json')
37
+ }
38
+
39
+ // emit a stable JSON document on stdout. no trailing prose, no ANSI,
40
+ // no colors. the 2-space indent matches the rest of the CLI and is
41
+ // cheap enough that pretty-printing by default is fine for bisect
42
+ // scripts — consumers who care pipe through `jq -c`.
43
+ export function printJson(payload: unknown): void {
44
+ process.stdout.write(`${JSON.stringify(payload ?? null, null, 2)}\n`)
45
+ }
46
+
47
+ // warnings, hints, "screen transition still active" messages — anything
48
+ // that isn't the payload. always goes to stderr so stdout stays parseable.
49
+ export function printWarn(msg: string): void {
50
+ process.stderr.write(`${msg}\n`)
51
+ }
52
+
53
+ // 24-hour wall-clock + relative offset for log/error/network timestamps.
54
+ // agents reading these care most about "is this new or stale" — append
55
+ // "+<n>s" for things younger than 60s. older entries keep the relative
56
+ // stamp in m/h/d so it's still scannable without diffing wall-clock times.
57
+ //
58
+ // returns "HH:MM:SS +Xs" where wall clock is locale-independent 24-hour.
59
+ // callers can wrap it in their own padding/dim ANSI.
60
+ export function formatLogTimestamp(ts: number, nowMs: number = Date.now()): string {
61
+ const d = new Date(ts)
62
+ const wall = `${pad2(d.getHours())}:${pad2(d.getMinutes())}:${pad2(d.getSeconds())}`
63
+ const deltaMs = nowMs - ts
64
+ if (deltaMs < 0) return wall
65
+ const sec = Math.round(deltaMs / 1000)
66
+ if (sec < 60) return `${wall} +${sec}s`
67
+ if (sec < 3600) return `${wall} +${Math.round(sec / 60)}m`
68
+ if (sec < 86400) return `${wall} +${Math.round(sec / 3600)}h`
69
+ return `${wall} +${Math.round(sec / 86400)}d`
70
+ }
71
+
72
+ function pad2(n: number): string {
73
+ return n < 10 ? `0${n}` : String(n)
74
+ }
75
+
76
+ export async function callTestBridge<T = unknown>(
77
+ bridge: WsBridge,
78
+ method: string,
79
+ ...args: unknown[]
80
+ ): Promise<T> {
81
+ return callInBridge<T>(bridge, `__sootsimTest.${method}`, ...args)
82
+ }
83
+
84
+ export async function callShellCommand<T = unknown>(
85
+ bridge: WsBridge,
86
+ method: 'launchApp' | 'goHome' | 'openSwitcher' | 'openSwitcherCard',
87
+ ...args: unknown[]
88
+ ): Promise<T> {
89
+ return callInBridgeWrite<T>(bridge, `SootSim.bridges.mainShell.${method}`, ...args)
90
+ }
91
+
92
+ export async function maybeWaitForStartedScreenTransitions(
93
+ bridge: WsBridge,
94
+ opts: { verbose?: boolean } = {},
95
+ ) {
96
+ if (bridge.plane === 'cloud') return
97
+ try {
98
+ const result = await callTestBridge<SootSimScreenTransitionWaitResult>(
99
+ bridge,
100
+ 'waitForScreenTransitions',
101
+ SCREEN_TRANSITION_PREFLIGHT,
102
+ )
103
+ if (!opts.verbose || !result?.started) return
104
+ if (result.timedOut) {
105
+ console.log(
106
+ ` screen transition still active after ${result.waitedMs}ms; continuing`,
107
+ )
108
+ return
109
+ }
110
+ if (result.waitedMs > 0) {
111
+ console.log(` waited ${result.waitedMs}ms for screen transition settle`)
112
+ }
113
+ } catch {
114
+ // best-effort preflight only — older runtimes or boot races should not
115
+ // block the actual command.
116
+ }
117
+ }
118
+
119
+ export async function callShellCommandWhenReady<T = unknown>(
120
+ bridge: WsBridge,
121
+ method: 'launchApp' | 'goHome' | 'openSwitcher' | 'openSwitcherCard',
122
+ readyTimeoutMs: number,
123
+ ...args: unknown[]
124
+ ): Promise<T> {
125
+ const deadline = Date.now() + Math.max(0, readyTimeoutMs)
126
+ while (true) {
127
+ try {
128
+ return await callShellCommand<T>(bridge, method, ...args)
129
+ } catch (error) {
130
+ if (!isShellCommandUnavailable(error) || Date.now() >= deadline) throw error
131
+ await sleep(50)
132
+ }
133
+ }
134
+ }
135
+
136
+ export async function waitForSimReady(
137
+ wsPort: number,
138
+ commandTimeoutMs: number,
139
+ simId?: string,
140
+ opts: {
141
+ attempts?: number
142
+ intervalMs?: number
143
+ minNodeCount?: number
144
+ simIdSource?: ParsedBridgeCliArgs['simIdSource']
145
+ } = {},
146
+ ) {
147
+ const attempts = opts.attempts ?? 30
148
+ const intervalMs = opts.intervalMs ?? 500
149
+ const minNodeCount = opts.minNodeCount ?? 10
150
+
151
+ for (let i = 0; i < attempts; i++) {
152
+ const bridge = createBridge(wsPort, {
153
+ commandTimeoutMs,
154
+ simId,
155
+ simIdSource: opts.simIdSource,
156
+ })
157
+ try {
158
+ const count = await bridge.send({
159
+ type: 'evaluate',
160
+ code: '(async () => (await window.__sootsimTest?.getNodeCount()) || 0)()',
161
+ })
162
+ if (typeof count === 'number' && count > minNodeCount) {
163
+ return { bridge, count }
164
+ }
165
+ } catch {
166
+ // still loading or disconnected
167
+ }
168
+ bridge.close()
169
+ await sleep(intervalMs)
170
+ }
171
+
172
+ return null
173
+ }
174
+
175
+ // waits until a bridge can evaluate a trivial expression. used by the reload
176
+ // full-page path after window.location.reload() to re-establish a bridge
177
+ // before handing off to pollForReloadReady. intentionally does not gate on
178
+ // node count — that check belongs in the subsequent ready poll, which uses
179
+ // the __sootsimExternalAppReady flag as its primary signal.
180
+ export async function waitForBridgeConnected(
181
+ wsPort: number,
182
+ commandTimeoutMs: number,
183
+ simId: string | undefined,
184
+ opts: {
185
+ timeoutMs?: number
186
+ intervalMs?: number
187
+ simIdSource?: ParsedBridgeCliArgs['simIdSource']
188
+ } = {},
189
+ ): Promise<WsBridge | null> {
190
+ const timeoutMs = opts.timeoutMs ?? 8000
191
+ const intervalMs = opts.intervalMs ?? 250
192
+ const deadline = Date.now() + timeoutMs
193
+ while (Date.now() < deadline) {
194
+ const bridge = createBridge(wsPort, {
195
+ commandTimeoutMs,
196
+ simId,
197
+ simIdSource: opts.simIdSource,
198
+ })
199
+ try {
200
+ await bridge.send({ type: 'evaluate', code: '1' })
201
+ return bridge
202
+ } catch {
203
+ bridge.close()
204
+ }
205
+ await sleep(intervalMs)
206
+ }
207
+ return null
208
+ }
209
+
210
+ export interface ReloadReadyResult {
211
+ ready: boolean
212
+ source: 'flag' | 'nodes-fallback' | 'error-bail' | 'timeout'
213
+ elapsedMs: number
214
+ nodes: number
215
+ targets: number
216
+ liveFrameActive: boolean
217
+ liveFrameChannels: number
218
+ liveFramePublishes: number
219
+ flag: unknown
220
+ loadingText: string
221
+ externalReady: boolean | null
222
+ externalStatus: string
223
+ externalError: string
224
+ errors: number
225
+ }
226
+
227
+ // post-reload readiness is the same engine-owned condition as `rnx wait ready`.
228
+ // keeping reload on that primitive prevents another polling implementation
229
+ // from drifting on loading text, live frames, or tree stability.
230
+ export async function pollForReloadReady(
231
+ bridge: WsBridge,
232
+ opts: { timeoutMs?: number } = {},
233
+ ): Promise<ReloadReadyResult> {
234
+ const timeoutMs = opts.timeoutMs ?? 10000
235
+ const status = await inspectWaitReady(bridge, timeoutMs)
236
+ const source = status.ready
237
+ ? status.flag === true
238
+ ? 'flag'
239
+ : 'nodes-fallback'
240
+ : status.errors > 0 && status.flag !== true
241
+ ? 'error-bail'
242
+ : 'timeout'
243
+ return {
244
+ ready: status.ready,
245
+ source,
246
+ elapsedMs: status.elapsedMs,
247
+ nodes: status.nodes,
248
+ targets: status.targets,
249
+ liveFrameActive: status.liveFrameActive,
250
+ liveFrameChannels: status.liveFrameChannels,
251
+ liveFramePublishes: status.liveFramePublishes,
252
+ flag: status.flag,
253
+ loadingText: status.loadingText,
254
+ externalReady: status.externalReady,
255
+ externalStatus: status.externalStatus,
256
+ externalError: status.externalError,
257
+ errors: status.errors,
258
+ }
259
+ }
@@ -0,0 +1,15 @@
1
+ import { rnxExit } from '../../run-rnx'
2
+ import { sleep } from './shared'
3
+
4
+ export async function runSleepSubcommand(opts: {
5
+ positional: string[]
6
+ inspectUsage: (name: string, tail: string) => string
7
+ }): Promise<void> {
8
+ const seconds = opts.positional[1] ? Number(opts.positional[1]) : 0.5
9
+ if (!Number.isFinite(seconds) || seconds < 0) {
10
+ console.error(opts.inspectUsage('sleep', '[seconds]'))
11
+ rnxExit(1)
12
+ }
13
+ await sleep(seconds * 1000)
14
+ console.log(` slept ${seconds}s`)
15
+ }
@@ -0,0 +1,23 @@
1
+ // `rnx get tree` — pruned structured semantic tree, default depth 5.
2
+
3
+ import { formatSemanticTree, inspectTree, isSimSemanticTree } from './core'
4
+ import { printJson, wantsJson } from './shared'
5
+ import type { WsBridge } from '../../ws-bridge'
6
+
7
+ export async function runTreeSubcommand(opts: {
8
+ bridge: WsBridge
9
+ args: string[]
10
+ positional: string[]
11
+ }): Promise<void> {
12
+ const { bridge, args, positional } = opts
13
+ // --boxes keeps the deeper default expected by geometry consumers. the
14
+ // semantic tree is structured in both modes.
15
+ const boxes = args.includes('--boxes')
16
+ const depth = positional[1] ? Number(positional[1]) : boxes ? 50 : 5
17
+ const { tree } = await inspectTree(bridge, depth)
18
+ if (wantsJson(args)) {
19
+ printJson({ depth, tree: tree ?? null })
20
+ return
21
+ }
22
+ console.log(isSimSemanticTree(tree) ? formatSemanticTree(tree) : '')
23
+ }
@@ -0,0 +1,17 @@
1
+ // `rnx get url` — prints the current page URL the bridge is attached to.
2
+
3
+ import { inspectUrl } from './core'
4
+ import { printJson, wantsJson } from './shared'
5
+ import type { WsBridge } from '../../ws-bridge'
6
+
7
+ export async function runUrlSubcommand(
8
+ bridge: WsBridge,
9
+ opts: { args: string[] } = { args: [] },
10
+ ): Promise<void> {
11
+ const result = await inspectUrl(bridge)
12
+ if (wantsJson(opts.args)) {
13
+ printJson(result)
14
+ return
15
+ }
16
+ console.log(result.url)
17
+ }
@@ -0,0 +1,211 @@
1
+ // rnx wait event — block until a semantic timeline event of the
2
+ // given kind appears, or timeout.
3
+ //
4
+ // usage:
5
+ // rnx wait event <kind> [--max-ms 5000] [--since now|cursor]
6
+ // rnx wait event fetch [--filter <substring>]
7
+ // rnx wait event screen [--equals SignInPage]
8
+ //
9
+ // the existing wait family is layout-driven: `wait selector` polls
10
+ // for a testID, `wait idle` polls for rAF + animation settling,
11
+ // `wait ready` polls for bundle paint. `wait event` is the
12
+ // timeline-driven companion: agents that want to drive "tap → wait
13
+ // for the BeginSignIn fetch to land → tap next" no longer need to
14
+ // chain `do tap-text Continue` + `sleep 4` + `what-happened --kinds
15
+ // fetch --since 4s | grep BeginSignIn`. one read returns when the
16
+ // next event of the requested kind appears.
17
+ //
18
+ // the implementation polls timeline.recent() — server-side
19
+ // subscription would be cheaper but a 200ms poll is plenty for the
20
+ // human-paced UI events we care about and stays compatible with the
21
+ // existing query surface.
22
+
23
+ import { rnxExit } from '../../run-rnx'
24
+ import type { WsBridge } from '../../ws-bridge'
25
+ import type {
26
+ SootSimTimelineEvent,
27
+ SootSimTimelineQuery,
28
+ SootSimTimelineQueryResult,
29
+ } from '@rnx/globals'
30
+
31
+ interface WaitEventOptions {
32
+ bridge: WsBridge
33
+ args: string[]
34
+ positional: string[]
35
+ inspectUsage: (name: string, tail: string) => string
36
+ }
37
+
38
+ // every timeline event kind the cli currently knows about. matching
39
+ // the type on `SootSimTimelineEvent['kind']` would be more strict
40
+ // but the timeline can grow new kinds and we don't want this
41
+ // command to refuse an unknown one — the worst case is the wait
42
+ // just times out, which is exactly the right behaviour.
43
+ const KNOWN_KINDS = new Set([
44
+ 'app-launch',
45
+ 'toast',
46
+ 'keyboard',
47
+ 'screen',
48
+ 'flow-step',
49
+ 'route',
50
+ 'alert',
51
+ 'actionsheet',
52
+ 'picker',
53
+ 'notification',
54
+ 'fetch',
55
+ 'console',
56
+ 'shell',
57
+ 'scroll',
58
+ 'gesture',
59
+ 'text-input',
60
+ 'react-commit',
61
+ 'animation',
62
+ 'reanimated',
63
+ ])
64
+
65
+ function findFlagValue(args: string[], flag: string): string | undefined {
66
+ const idx = args.indexOf(flag)
67
+ if (idx >= 0 && idx + 1 < args.length) return args[idx + 1]
68
+ return undefined
69
+ }
70
+
71
+ function eventMatches(
72
+ ev: SootSimTimelineEvent,
73
+ opts: { filter?: string; equals?: string },
74
+ ): boolean {
75
+ if (!opts.filter && !opts.equals) return true
76
+ const data = ev.data as Record<string, unknown> | null
77
+ // build a one-string haystack from the most-likely-relevant fields
78
+ // per kind. agents asking for `wait event fetch --filter BeginSignIn`
79
+ // expect to match the URL; `wait event screen --equals Inbox`
80
+ // expects to match the screen name.
81
+ const haystack: string[] = []
82
+ if (data && typeof data === 'object') {
83
+ for (const key of [
84
+ 'url',
85
+ 'displayUrl',
86
+ 'message',
87
+ 'name',
88
+ 'activeName',
89
+ 'path',
90
+ 'pathname',
91
+ 'title',
92
+ 'phase',
93
+ 'event',
94
+ 'type',
95
+ 'kind',
96
+ ]) {
97
+ const v = data[key]
98
+ if (typeof v === 'string' && v.length > 0) haystack.push(v)
99
+ }
100
+ }
101
+ const joined = haystack.join(' ')
102
+ if (opts.equals) {
103
+ return haystack.some((s) => s === opts.equals)
104
+ }
105
+ if (opts.filter) {
106
+ return joined.toLowerCase().includes(opts.filter.toLowerCase())
107
+ }
108
+ return true
109
+ }
110
+
111
+ export async function runWaitEventSubcommand(opts: WaitEventOptions): Promise<void> {
112
+ const { bridge, args, positional, inspectUsage } = opts
113
+ // positional shape after verb-prefix strip and stripBooleanFlags:
114
+ // rnx wait event <kind> → ['event', '<kind>']
115
+ // so positional[1] is the kind.
116
+ const kind = positional[1]
117
+ if (!kind) {
118
+ console.error(
119
+ inspectUsage(
120
+ 'event',
121
+ '<kind> [--max-ms 5000] [--filter <substring>] [--equals <exact>] [--since now|cursor]',
122
+ ),
123
+ )
124
+ rnxExit(1)
125
+ }
126
+ if (!KNOWN_KINDS.has(kind)) {
127
+ console.error(
128
+ ` warning: '${kind}' is not a known timeline kind — waiting anyway. known: ${[...KNOWN_KINDS].sort().join(', ')}`,
129
+ )
130
+ }
131
+
132
+ const maxMsRaw = findFlagValue(args, '--max-ms')
133
+ const maxMs =
134
+ maxMsRaw && Number.isFinite(Number(maxMsRaw)) ? Math.max(100, Number(maxMsRaw)) : 5000
135
+
136
+ const filter = findFlagValue(args, '--filter')
137
+ const equals = findFlagValue(args, '--equals')
138
+ const sinceMode = findFlagValue(args, '--since') ?? 'now'
139
+ const jsonOut = args.includes('--json')
140
+
141
+ // capture the start watermark so we don't match an event that
142
+ // happened before this command. agents calling `wait event fetch`
143
+ // after a tap mean "the *next* fetch", not "any fetch since the
144
+ // session started". `--since cursor` falls back to the
145
+ // what-happened cursor semantics for callers that explicitly want
146
+ // that.
147
+ const startedAt = Date.now()
148
+ const deadline = startedAt + maxMs
149
+ const pollMs = 200
150
+
151
+ let lastWatermark = startedAt
152
+
153
+ while (Date.now() < deadline) {
154
+ const query: SootSimTimelineQuery = {
155
+ kinds: [kind] as SootSimTimelineQuery['kinds'],
156
+ since: sinceMode === 'cursor' ? undefined : lastWatermark,
157
+ limit: 50,
158
+ }
159
+ const result = (await bridge.send({
160
+ type: 'evaluate',
161
+ // hand-marshal the query through evaluate — the bridge already
162
+ // exposes `SootSim.bridges.timeline.recent` as a sync method,
163
+ // but evaluate is the universally available shape and the cost
164
+ // of a string-baked function is negligible at 5Hz.
165
+ code: `(async () => {
166
+ const t = window.SootSim?.bridges?.timeline
167
+ if (!t) return { ok: false, error: 'timeline bridge missing' }
168
+ return { ok: true, result: await t.recent(${JSON.stringify(query)}) }
169
+ })()`,
170
+ })) as { ok: true; result: SootSimTimelineQueryResult } | { ok: false; error: string }
171
+ if (!result || !result.ok) {
172
+ console.error(
173
+ ` could not query timeline: ${result && 'error' in result ? result.error : 'unknown'}`,
174
+ )
175
+ rnxExit(1)
176
+ }
177
+ const evts = result.result.events ?? []
178
+ for (const ev of evts) {
179
+ if (eventMatches(ev, { filter, equals })) {
180
+ const elapsed = Date.now() - startedAt
181
+ if (jsonOut) {
182
+ console.log(JSON.stringify({ found: true, elapsedMs: elapsed, event: ev }))
183
+ } else {
184
+ console.log(
185
+ ` ${kind} event after ${elapsed}ms${filter ? ` (filter: ${filter})` : ''}${equals ? ` (equals: ${equals})` : ''}`,
186
+ )
187
+ }
188
+ return
189
+ }
190
+ }
191
+ // advance the local watermark to the high-water mark we just
192
+ // saw so the next poll doesn't redundantly re-scan the same
193
+ // events.
194
+ if (result.result.watermark && result.result.watermark > lastWatermark) {
195
+ lastWatermark = result.result.watermark
196
+ }
197
+ await new Promise((r) => setTimeout(r, pollMs))
198
+ }
199
+
200
+ const elapsed = Date.now() - startedAt
201
+ if (jsonOut) {
202
+ console.log(
203
+ JSON.stringify({ found: false, elapsedMs: elapsed, kind, filter, equals }),
204
+ )
205
+ } else {
206
+ console.error(
207
+ ` ⚠ wait event ${kind} timed out after ${elapsed}ms${filter ? ` (filter: ${filter})` : ''}${equals ? ` (equals: ${equals})` : ''}`,
208
+ )
209
+ }
210
+ rnxExit(1)
211
+ }
@@ -0,0 +1,25 @@
1
+ import { rnxExit } from '../../run-rnx'
2
+ import { resolveMaxMsFlag } from './core'
3
+ import { waitForSootsimIdle } from './settling'
4
+ import type { WsBridge } from '../../ws-bridge'
5
+
6
+ // alias for `do settle` under the discoverable `wait` verb grouping.
7
+ // layout hash stability across 3 polls; --strict also requires worker
8
+ // animation flags to be clear.
9
+ export async function runWaitIdleSubcommand(opts: {
10
+ bridge: WsBridge
11
+ args: string[]
12
+ positional: string[]
13
+ }): Promise<void> {
14
+ const { bridge, args } = opts
15
+ const strict = args.includes('--strict')
16
+ const maxMs = resolveMaxMsFlag(args, 3000)
17
+ const { elapsed, settled } = await waitForSootsimIdle({ bridge, maxMs, strict })
18
+
19
+ if (settled) {
20
+ console.log(` idle in ${elapsed}ms`)
21
+ } else {
22
+ console.error(` ⚠ wait idle timed out after ${elapsed}ms (may still be animating)`)
23
+ rnxExit(1)
24
+ }
25
+ }
@@ -0,0 +1,87 @@
1
+ import { rnxExit } from '../../run-rnx'
2
+ import { inspectWaitReady, resolveMaxMsFlag, waitReadyReason } from './core'
3
+ import { waitForSootsimIdle } from './settling'
4
+ import type { WsBridge } from '../../ws-bridge'
5
+
6
+ // blocks until the guest app bundle has finished loading and painted real
7
+ // inspectable content. the persistent window flag
8
+ // `__sootsimExternalAppReady` is necessary but not sufficient: shell-owned
9
+ // canvas can expose a stable launch/loading tree before the app content lands.
10
+ // the engine owns the polling loop; this handler sends one request, then
11
+ // reports the typed result.
12
+ export async function runWaitReadySubcommand(opts: {
13
+ bridge: WsBridge
14
+ args: string[]
15
+ }): Promise<void> {
16
+ const { bridge, args } = opts
17
+ // default 20s because cold guest bundles (uniswap, expensify, bluesky)
18
+ // routinely take 10-15s to evaluate + mount.
19
+ const timeoutMs = resolveMaxMsFlag(args, 20000)
20
+
21
+ const {
22
+ ready,
23
+ elapsedMs,
24
+ nodes,
25
+ targets,
26
+ liveFrameActive,
27
+ liveFrameChannels,
28
+ flag,
29
+ loadingText,
30
+ externalReady,
31
+ externalStatus,
32
+ externalError,
33
+ suppressedEntryError,
34
+ errors,
35
+ bridgeError,
36
+ } = await inspectWaitReady(bridge, timeoutMs, {
37
+ ...(bridge.plane !== 'cloud'
38
+ ? {
39
+ onProgress(status) {
40
+ console.error(
41
+ ` still waiting after ${status.elapsedMs}ms — ${waitReadyReason(status)} (nodes: ${status.nodes}, targets: ${status.targets}, live surfaces: ${status.liveFrameActive ? status.liveFrameChannels : 0}, errors: ${status.errors})`,
42
+ )
43
+ },
44
+ }
45
+ : {}),
46
+ })
47
+ if (ready) {
48
+ if (bridge.plane === 'cloud') {
49
+ console.log(
50
+ ` ready in ${elapsedMs}ms: ${nodes} nodes, ${targets} targets${liveFrameActive ? `, ${liveFrameChannels} live surfaces` : ''}`,
51
+ )
52
+ return
53
+ }
54
+ const remainingMs = timeoutMs - elapsedMs
55
+ const idleBudgetMs = Math.max(100, Math.min(10_000, remainingMs))
56
+ const idle = await waitForSootsimIdle({
57
+ bridge,
58
+ maxMs: idleBudgetMs,
59
+ pollMs: 32,
60
+ stablePolls: 2,
61
+ })
62
+ if (!idle.settled) {
63
+ console.error(
64
+ ` ⚠ wait ready timed out after ${elapsedMs + idle.elapsed}ms — app mounted but did not settle (nodes: ${nodes}, targets: ${targets})`,
65
+ )
66
+ rnxExit(1)
67
+ }
68
+ console.log(
69
+ ` ready in ${elapsedMs + idle.elapsed}ms: ${nodes} nodes, ${targets} targets${liveFrameActive ? `, ${liveFrameChannels} live surfaces` : ''}`,
70
+ )
71
+ return
72
+ }
73
+ const reason = waitReadyReason({
74
+ externalError,
75
+ loadingText,
76
+ externalReady,
77
+ externalStatus,
78
+ flag,
79
+ targets,
80
+ suppressedEntryError,
81
+ bridgeError,
82
+ })
83
+ console.error(
84
+ ` ⚠ wait ready timed out after ${elapsedMs}ms — ${reason} (nodes: ${nodes}, targets: ${targets}, live surfaces: ${liveFrameActive ? liveFrameChannels : 0}, errors: ${errors})`,
85
+ )
86
+ rnxExit(1)
87
+ }