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,17 @@
1
+ // `rnx get count` — total SootSimNode count in the live tree.
2
+
3
+ import { inspectNodeCount } from './core'
4
+ import { printJson, wantsJson } from './shared'
5
+ import type { WsBridge } from '../../ws-bridge'
6
+
7
+ export async function runCountSubcommand(
8
+ bridge: WsBridge,
9
+ opts: { args: string[] } = { args: [] },
10
+ ): Promise<void> {
11
+ const result = await inspectNodeCount(bridge)
12
+ if (wantsJson(opts.args)) {
13
+ printJson(result)
14
+ return
15
+ }
16
+ console.log(` nodes: ${result.nodes}`)
17
+ }
@@ -0,0 +1,119 @@
1
+ // `rnx describe` — format the pruned visible semantic tree.
2
+
3
+ import { maybeHint } from '../../hints'
4
+ import { inspectDescribe, type DescribeDumpOptions } from './core'
5
+ import {
6
+ maybeWaitForStartedScreenTransitions,
7
+ printJson,
8
+ sleep,
9
+ wantsJson,
10
+ } from './shared'
11
+ import type { WsBridge } from '../../ws-bridge'
12
+
13
+ export interface DescribeOptions {
14
+ bridge: WsBridge
15
+ args: string[]
16
+ positional: string[]
17
+ }
18
+
19
+ function flagValue(args: string[], name: string): string | null {
20
+ const i = args.indexOf(name)
21
+ return i >= 0 && i + 1 < args.length ? args[i + 1] : null
22
+ }
23
+
24
+ export async function runDescribeSubcommand(opts: DescribeOptions): Promise<void> {
25
+ const { bridge, args, positional } = opts
26
+ const verbose = args.includes('--verbose') || args.includes('-v')
27
+ const jsonOut = wantsJson(args)
28
+ const verboseDescribeTransitions = verbose && !jsonOut
29
+ const watch = args.includes('--watch') || args.includes('-w')
30
+ const watchInterval = 1000
31
+ const compact = args.includes('--compact')
32
+ const hideXy = args.includes('--no-xy')
33
+ const testIdLike = flagValue(args, '--testid-like')
34
+ const onlyGlob = flagValue(args, '--only')
35
+ const subtreeRoot = flagValue(args, '--subtree')
36
+
37
+ // bare positional still works — routes to --only if it looks globby,
38
+ // otherwise preserves the old substring-match behavior as `filter`. guard
39
+ // against a flag-looking token leaking in as a filter: an unrecognized
40
+ // `--flag` that the arg parser didn't strip must never become a text filter
41
+ // (that's how `describe --compact` used to match nothing).
42
+ const positionalFilter =
43
+ positional[1] && !positional[1].startsWith('-') ? positional[1] : undefined
44
+ const bareIsGlob = positionalFilter ? /[*?]/.test(positionalFilter) : false
45
+ const filter = !bareIsGlob && !onlyGlob ? positionalFilter : undefined
46
+ const effectiveOnlyGlob = onlyGlob ?? (bareIsGlob ? positionalFilter : undefined)
47
+
48
+ const runOnce = async () => {
49
+ await maybeWaitForStartedScreenTransitions(bridge, {
50
+ verbose: verboseDescribeTransitions,
51
+ })
52
+ const dumpOpts: DescribeDumpOptions = {
53
+ describe: true,
54
+ verbose,
55
+ filter: filter || '',
56
+ testIdLike: testIdLike || undefined,
57
+ onlyGlob: effectiveOnlyGlob || undefined,
58
+ subtreeRoot: subtreeRoot || undefined,
59
+ compact,
60
+ hideXy,
61
+ // no terminal to fit, so nothing gets clipped on the way out
62
+ fullText: jsonOut,
63
+ }
64
+ const result = await inspectDescribe(bridge, dumpOpts)
65
+ const tree = result?.tree
66
+
67
+ if (jsonOut) {
68
+ printJson({ tree: tree ?? '' })
69
+ return
70
+ }
71
+
72
+ if (typeof tree === 'string' && tree.startsWith('__SUBTREE_NOT_FOUND__:')) {
73
+ const missing = tree.slice('__SUBTREE_NOT_FOUND__:'.length)
74
+ console.log(` subtree root not found: ${missing}`)
75
+ maybeHint('subtree-root-not-found', missing)
76
+ return
77
+ }
78
+
79
+ if (!tree) {
80
+ const nodeCount = result?.nodeCount ?? 0
81
+ console.log(' no matching nodes found')
82
+ const anyFilterActive = filter || testIdLike || effectiveOnlyGlob || subtreeRoot
83
+ if (!anyFilterActive && nodeCount < 10) {
84
+ maybeHint('app-still-loading', nodeCount)
85
+ }
86
+ return
87
+ }
88
+
89
+ console.log(tree)
90
+
91
+ const anyFilterActive = filter || testIdLike || effectiveOnlyGlob || subtreeRoot
92
+ const searchFilterActive = filter || testIdLike || effectiveOnlyGlob
93
+ if (searchFilterActive && !watch) {
94
+ maybeHint('describe-filter-context')
95
+ }
96
+
97
+ // when no filter was active and the tree got long, nudge once per
98
+ // session toward the narrowing flags — this is exactly the case the
99
+ // user flagged where "which row is the swap sheet" got buried under
100
+ // 100+ lines of home content.
101
+ if (!anyFilterActive && !watch) {
102
+ const lineCount = tree.split('\n').length
103
+ if (lineCount >= 80) {
104
+ maybeHint('describe-use-filters')
105
+ }
106
+ }
107
+ }
108
+
109
+ if (watch) {
110
+ console.log(' watching... (Ctrl+C to stop)\n')
111
+ while (true) {
112
+ console.clear()
113
+ await runOnce()
114
+ await sleep(watchInterval)
115
+ }
116
+ } else {
117
+ await runOnce()
118
+ }
119
+ }
@@ -0,0 +1,25 @@
1
+ // env vars that indicate the CLI is being driven by an agent/automation rather
2
+ // than a human at a terminal. when any is present, auto-waits get a longer
3
+ // budget and log more aggressively, because an agent can't see mid-animation
4
+ // flicker the way a human can.
5
+ const AGENT_ENV_VARS = [
6
+ 'RNX_AGENT',
7
+ 'TM_SESSION',
8
+ 'CLAUDECODE',
9
+ 'CLAUDE_CODE_ENTRYPOINT',
10
+ 'CLAUDE_CODE_SESSION_ID',
11
+ 'CODEX_THREAD_ID',
12
+ 'CURSOR_TRACE_ID',
13
+ 'AIDER_MODEL',
14
+ ]
15
+
16
+ export function isAgentEnv(
17
+ env: Readonly<Record<string, string | undefined>> = process.env,
18
+ ): boolean {
19
+ if (env.RNX_AGENT === '0') return false
20
+ for (const name of AGENT_ENV_VARS) {
21
+ const v = env[name]
22
+ if (v && v.trim() && v !== '0') return true
23
+ }
24
+ return false
25
+ }
@@ -0,0 +1,169 @@
1
+ import { maybeHint } from '../../hints'
2
+ import { rnxExit } from '../../run-rnx'
3
+ import { inspectFind, inspectNodeCount, rankInteractive, tapCommandForNode } from './core'
4
+ import { printJson, wantsJson } from './shared'
5
+ import type { WsBridge } from '../../ws-bridge'
6
+
7
+ // unified finder: one verb for text/testid/role/type/pressable/visible.
8
+ // --testid (not --id) keeps element lookup distinct from target selection.
9
+ // mode resolution + the evaluated JS live in inspect/core.ts (shared with the
10
+ // agent); this handler only parses flags and formats the result for the CLI.
11
+ export async function runFindSubcommand(opts: {
12
+ bridge: WsBridge
13
+ args: string[]
14
+ effectiveArgs: string[]
15
+ positional: string[]
16
+ inspectUsage: (name: string, tail: string) => string
17
+ }): Promise<void> {
18
+ const { bridge, args, effectiveArgs, positional, inspectUsage } = opts
19
+
20
+ const findFlag = (name: string): string | null => {
21
+ const i = effectiveArgs.indexOf(name)
22
+ return i >= 0 && i + 1 < effectiveArgs.length ? effectiveArgs[i + 1] : null
23
+ }
24
+ const hasFlag = (name: string): boolean => effectiveArgs.includes(name)
25
+
26
+ const testId = findFlag('--testid') || findFlag('--test-id')
27
+ const role = findFlag('--role')
28
+ const typeFlag = findFlag('--type')
29
+ const textFlag = findFlag('--text')
30
+ const pressable = hasFlag('--pressable')
31
+ const visible = hasFlag('--visible')
32
+ // ranked-tappable list — same data source as --pressable, but the
33
+ // output prioritises high-signal targets the agent likely wants to
34
+ // tap next (visible, has text or testID, has accessibilityLabel),
35
+ // and emits a copy-paste `rnx do` command per row so the agent
36
+ // doesn't have to re-form a tap from raw fields.
37
+ const interactive = hasFlag('--interactive-targets') || hasFlag('--actions')
38
+ const bareText =
39
+ !testId && !role && !typeFlag && !textFlag && !pressable && !visible && !interactive
40
+ ? positional[1]
41
+ : null
42
+ const textQuery = textFlag ?? bareText
43
+
44
+ const found = await inspectFind(bridge, {
45
+ testId,
46
+ role,
47
+ type: typeFlag,
48
+ text: textQuery,
49
+ pressable,
50
+ visible,
51
+ interactive,
52
+ })
53
+ if (!found) {
54
+ console.error(
55
+ inspectUsage(
56
+ 'find',
57
+ '<text> | --text <t> | --testid <id> | --role <r> | --type <t> | --pressable | --visible | --interactive-targets',
58
+ ),
59
+ )
60
+ rnxExit(1)
61
+ }
62
+ const { mode, result } = found
63
+ const jsonOut = wantsJson(args)
64
+ // --verbose / --dump: emit full node JSON per result instead of just the
65
+ // one-line summary. the default summary is compact for scannability; when
66
+ // you already know the match and need to copy testID / layout / a11y
67
+ // props, --verbose saves the round-trip through `get node <id>`.
68
+ const verbose = args.includes('--verbose') || args.includes('--dump')
69
+
70
+ if (jsonOut) {
71
+ if (mode === 'interactive-targets' && Array.isArray(result)) {
72
+ // include the `tap` command per row when emitting JSON for agents
73
+ // — the human renderer does the same. tooling that drives rnx
74
+ // can pick the highest-scored entry and execute its `tap` field.
75
+ printJson(rankInteractive(result).map((n) => ({ ...n, tap: tapCommandForNode(n) })))
76
+ } else {
77
+ printJson(result ?? null)
78
+ }
79
+ } else if (Array.isArray(result)) {
80
+ if (result.length === 0) {
81
+ console.log(` no ${mode} nodes found`)
82
+ const { nodes: nodeCount } = await inspectNodeCount(bridge)
83
+ if (nodeCount < 10) {
84
+ maybeHint('app-still-loading', nodeCount)
85
+ }
86
+ } else if (mode === 'interactive-targets') {
87
+ const ranked = rankInteractive(result)
88
+ console.log(
89
+ ` found ${ranked.length} interactive target${ranked.length === 1 ? '' : 's'} (sorted by score):`,
90
+ )
91
+ for (const n of ranked.slice(0, 20)) {
92
+ const loc = n.absolutePosition
93
+ ? `@(${Math.round(n.absolutePosition.x)},${Math.round(n.absolutePosition.y)})`
94
+ : ''
95
+ const size = n.layout
96
+ ? `${Math.round(n.layout.width)}x${Math.round(n.layout.height)}`
97
+ : '?x?'
98
+ const text = n.text ? ` "${n.text.slice(0, 30)}"` : ''
99
+ const tid = n.testID ? ` #${n.testID}` : ''
100
+ const label = n.accessibilityLabel
101
+ ? ` ⓘ"${String(n.accessibilityLabel).slice(0, 24)}"`
102
+ : ''
103
+ const roleName = n.accessibilityRole ? `[${n.accessibilityRole}]` : n.type
104
+ const tapCmd = tapCommandForNode(n)
105
+ console.log(` ${roleName}${text}${label}${tid} ${size} ${loc}`)
106
+ console.log(` → ${tapCmd}`)
107
+ if (verbose) console.log(indent(JSON.stringify(n, null, 2), ' '))
108
+ }
109
+ if (ranked.length > 20) console.log(` ... and ${ranked.length - 20} more`)
110
+ } else {
111
+ console.log(
112
+ ` found ${result.length} node${result.length === 1 ? '' : 's'} (${mode}):`,
113
+ )
114
+ for (const n of result.slice(0, 20)) {
115
+ const loc = n.absolutePosition
116
+ ? `@(${Math.round(n.absolutePosition.x)},${Math.round(n.absolutePosition.y)})`
117
+ : ''
118
+ const size = n.layout
119
+ ? `${Math.round(n.layout.width)}x${Math.round(n.layout.height)}`
120
+ : '?x?'
121
+ const text = n.text ? ` "${n.text.slice(0, 30)}"` : ''
122
+ const tid = n.testID ? ` #${n.testID}` : ''
123
+ const tap = n.pressable ? ' (tap)' : ''
124
+ const roleName = n.accessibilityRole ? `[${n.accessibilityRole}]` : n.type
125
+ console.log(` ${roleName}${text}${tid} ${size} ${loc}${tap}`)
126
+ if (verbose) console.log(indent(JSON.stringify(n, null, 2), ' '))
127
+ }
128
+ if (result.length > 20) console.log(` ... and ${result.length - 20} more`)
129
+ }
130
+ } else if (result == null) {
131
+ const query = textQuery || testId || role || typeFlag || ''
132
+ console.log(` not found: ${query || mode}`)
133
+ if (testId) {
134
+ maybeHint('wait-selector-for-missing-testid', testId)
135
+ }
136
+ } else {
137
+ interface InspectNode {
138
+ type?: string
139
+ absolutePosition?: { x: number; y: number }
140
+ layout?: { width: number; height: number }
141
+ text?: string
142
+ testID?: string
143
+ pressable?: boolean
144
+ accessibilityRole?: string
145
+ }
146
+ const n = result as InspectNode
147
+ if (n.type && n.absolutePosition) {
148
+ const loc = `@(${Math.round(n.absolutePosition.x)},${Math.round(n.absolutePosition.y)})`
149
+ const size = n.layout
150
+ ? `${Math.round(n.layout.width)}x${Math.round(n.layout.height)}`
151
+ : '?x?'
152
+ const text = n.text ? ` "${n.text.slice(0, 40)}"` : ''
153
+ const tid = n.testID ? ` #${n.testID}` : ''
154
+ const tap = n.pressable ? ' (tap)' : ''
155
+ const roleName = n.accessibilityRole ? `[${n.accessibilityRole}]` : n.type
156
+ console.log(` ${roleName}${text}${tid} ${size} ${loc}${tap}`)
157
+ if (verbose) console.log(indent(JSON.stringify(n, null, 2), ' '))
158
+ } else {
159
+ console.log(JSON.stringify(result, null, 2))
160
+ }
161
+ }
162
+ }
163
+
164
+ function indent(text: string, prefix: string): string {
165
+ return text
166
+ .split('\n')
167
+ .map((line) => prefix + line)
168
+ .join('\n')
169
+ }
@@ -0,0 +1,53 @@
1
+ // `rnx get-layout` — bounding boxes for visible meaningful elements on the
2
+ // current screen. focused on the layout numbers a designer checks: on-screen
3
+ // box (x/y/w/h). pass --styling for computed borderRadius, per-side padding,
4
+ // fontSize, visual style fields, and native rendered text contrast. pass
5
+ // --filter <text> to narrow to the elements whose text/testID/role/selector
6
+ // match — fewer rows, full detail on each, which is how you read one area's
7
+ // styling without the whole screen.
8
+ //
9
+ // the extraction + formatting live in the shared `./core` kernel
10
+ // (inspectGetLayout / formatLayoutElements) so this CLI command and the agent's
11
+ // app_get_layout tool produce identical output. the CLI-side job is just:
12
+ // optional preflight for in-flight screen transitions, call the kernel, print.
13
+
14
+ import { formatLayoutElements, inspectGetLayout } from './core'
15
+ import { maybeWaitForStartedScreenTransitions, printJson, wantsJson } from './shared'
16
+ import type { WsBridge } from '../../ws-bridge'
17
+
18
+ export async function runGetLayoutSubcommand(opts: {
19
+ bridge: WsBridge
20
+ args: string[]
21
+ }): Promise<void> {
22
+ const { bridge, args } = opts
23
+ const jsonOut = wantsJson(args)
24
+ await maybeWaitForStartedScreenTransitions(bridge, { verbose: !jsonOut })
25
+ const styling = args.includes('--styling')
26
+ const filterIndex = args.indexOf('--filter')
27
+ const filter = filterIndex >= 0 ? args[filterIndex + 1] : undefined
28
+ const elements = await inspectGetLayout(bridge, { styling, filter }).catch(
29
+ (error: unknown) => {
30
+ if (
31
+ styling &&
32
+ error instanceof Error &&
33
+ (error.message === 'unsupported RNX Cloud command type: evaluate' ||
34
+ error.message.includes('requires the debug scope'))
35
+ ) {
36
+ throw new Error(
37
+ 'get layout --styling is unavailable on a remote simulator; rerun without --styling or use a local sim',
38
+ )
39
+ }
40
+ throw error
41
+ },
42
+ )
43
+ if (jsonOut) {
44
+ printJson({ count: elements.length, elements })
45
+ return
46
+ }
47
+ console.log(
48
+ ` layout (${elements.length} element${elements.length === 1 ? '' : 's'}):\n`,
49
+ )
50
+ // styling fields (including native rendered text contrast) are opt-in via
51
+ // --styling so the default inspect output stays scannable.
52
+ console.log(formatLayoutElements(elements, { styling }))
53
+ }
@@ -0,0 +1,54 @@
1
+ // `rnx get keyboard` — prints the current iOS keyboard's full state.
2
+ //
3
+ // shows visibility, keyboardType, returnKeyType, autoCapitalize policy,
4
+ // active QWERTY sub-mode, shift + capsLock, and the mounted accessory bar
5
+ // id (if any). reads from `__sootsimKeyboard.getLayout()`, which is
6
+ // populated by shell-worker `keyboard.layout` broadcasts.
7
+
8
+ import { rnxExit } from '../../run-rnx'
9
+ import { inspectKeyboard } from './core'
10
+ import type { WsBridge } from '../../ws-bridge'
11
+
12
+ export async function runKeyboardSubcommand(
13
+ bridge: WsBridge,
14
+ opts: { json?: boolean } = {},
15
+ ): Promise<void> {
16
+ const result = await inspectKeyboard(bridge)
17
+
18
+ if ('error' in result) {
19
+ console.error(result.error)
20
+ rnxExit(1)
21
+ }
22
+
23
+ if (opts.json) {
24
+ console.log(JSON.stringify(result, null, 2))
25
+ return
26
+ }
27
+
28
+ const { visible, spec, mode, shifted, capsLock, accessoryBarId } = result
29
+ const lines: string[] = []
30
+ lines.push(`keyboard: ${visible ? 'visible' : 'hidden'}`)
31
+ if (spec) {
32
+ lines.push(` type: ${spec.keyboardType}`)
33
+ lines.push(` returnKey: ${spec.returnKeyType}`)
34
+ lines.push(` autoCap: ${spec.autoCapitalize}`)
35
+ lines.push(` autoCorrect: ${spec.autoCorrect ? 'on' : 'off'}`)
36
+ lines.push(` spellCheck: ${spec.spellCheck ? 'on' : 'off'}`)
37
+ lines.push(` appearance: ${spec.keyboardAppearance}`)
38
+ if (spec.secureTextEntry) lines.push(` secureTextEntry: true`)
39
+ if (spec.enablesReturnKeyAutomatically) {
40
+ lines.push(
41
+ ` return: ${spec.currentTextIsEmpty ? 'disabled (empty)' : 'enabled'}`,
42
+ )
43
+ }
44
+ } else {
45
+ lines.push(` spec: <none> (shown via dev-tools with no TextInput)`)
46
+ }
47
+ lines.push(
48
+ ` mode: ${mode}${shifted ? ' (shifted)' : ''}${capsLock ? ' (caps)' : ''}`,
49
+ )
50
+ if (accessoryBarId) {
51
+ lines.push(` accessoryBar: ${accessoryBarId}`)
52
+ }
53
+ console.log(lines.join('\n'))
54
+ }
@@ -0,0 +1,175 @@
1
+ // `rnx list` — connected sims, with the active one marked.
2
+
3
+ import { resolveDevCheckoutRuntimeRoot } from '../../../src/runtime-assets'
4
+ import { type BridgeSimInfo, formatLockOwner, type WsBridge } from '../../ws-bridge'
5
+ import { printJson, wantsJson } from './shared'
6
+
7
+ export async function runListSubcommand(opts: {
8
+ bridge: WsBridge
9
+ simId: string | undefined
10
+ args: string[]
11
+ }): Promise<void> {
12
+ const sims = await opts.bridge.listSims()
13
+
14
+ // filter sims:
15
+ // --all show every sim including empty shells (default
16
+ // hides sims that have no bundle URL — they're
17
+ // usually idle host shells that overflow the
18
+ // list when several demos are open at once)
19
+ // --bundle <substring> only sims whose loaded url contains the substring
20
+ // --app-port <port> shorthand for `--bundle :<port>/`. NOT `--port`
21
+ // which is taken by the global bridge-port flag
22
+ // (see ws-bridge.ts parseBridgeCliArgs) — using
23
+ // --port here used to silently set the bridge
24
+ // port too, sending the WS connect to the wrong
25
+ // place and failing with "daemon is not running"
26
+ // --primary only the primary sim
27
+ const showAll = opts.args.includes('--all')
28
+ const bundleArg = opts.args.find((_, i) => opts.args[i - 1] === '--bundle')
29
+ const portArg = opts.args.find((_, i) => opts.args[i - 1] === '--app-port')
30
+ const primaryOnly = opts.args.includes('--primary')
31
+ const filtered = sims.filter((sim) => {
32
+ if (primaryOnly && !sim.isPrimary) return false
33
+ if (bundleArg && !(sim.url ?? '').includes(bundleArg)) return false
34
+ // a sim url looks like `http://localhost:5173/rn/8085?bundle=...` where
35
+ // `8085` is the metro/vite port the app is loaded from. the bundle url
36
+ // inside the `?bundle=` param is itself percent-encoded (`:8085/` ->
37
+ // `%3A8085%2F`), so the previous literal `:${port}/` substring check
38
+ // never matched. match the canonical `/rn/<port>` path segment instead.
39
+ if (portArg && !(sim.url ?? '').includes(`/rn/${portArg}`)) return false
40
+ if (!showAll && !bundleArg && !portArg && !primaryOnly) {
41
+ // hide host shells (no bundle loaded — only origin) unless they're
42
+ // the currently-selected sim
43
+ const hasBundle =
44
+ sim.url && (sim.url.includes('bundle=') || sim.url.includes('/index.bundle'))
45
+ if (!hasBundle && sim.id !== opts.simId) return false
46
+ }
47
+ return true
48
+ })
49
+
50
+ if (wantsJson(opts.args)) {
51
+ printJson(filtered.map((b) => ({ ...b, active: b.id === opts.simId })))
52
+ return
53
+ }
54
+ printConnectedSims(filtered, opts.simId)
55
+ if (filtered.length < sims.length && !wantsJson(opts.args)) {
56
+ console.log(` (${sims.length - filtered.length} more hidden — pass --all to show)`)
57
+ }
58
+ }
59
+
60
+ // returns a short, human-friendly description of what a sim is showing —
61
+ // e.g. "bundle host/...path", "connect :8081", "demo bluesky". returns null
62
+ // when there's no useful summary beyond the raw URL itself, so the caller
63
+ // can skip the redundant `loaded:` line in that case.
64
+ function summarizeSimUrl(url: string | undefined): string | null {
65
+ if (!url) return null
66
+ try {
67
+ const parsed = new URL(url)
68
+ if (parsed.searchParams.has('bundle')) {
69
+ const bundleUrl = parsed.searchParams.get('bundle') || ''
70
+ try {
71
+ const bundle = new URL(bundleUrl)
72
+ const shortPath =
73
+ bundle.pathname.length > 36
74
+ ? `...${bundle.pathname.slice(-36)}`
75
+ : bundle.pathname
76
+ return `bundle ${bundle.host}${shortPath}`
77
+ } catch {
78
+ return 'bundle'
79
+ }
80
+ }
81
+ if (parsed.searchParams.has('port')) {
82
+ return `connect :${parsed.searchParams.get('port') || ''}`
83
+ }
84
+ if (parsed.searchParams.has('open')) {
85
+ return `connect ${parsed.searchParams.get('open') || ''}`
86
+ }
87
+ if (parsed.searchParams.has('demo')) {
88
+ return `demo ${parsed.searchParams.get('demo') || 'default'}`
89
+ }
90
+ if (
91
+ parsed.pathname.includes('/sootsim/index.html') ||
92
+ parsed.pathname === '/sootsim/' ||
93
+ parsed.pathname === '/sootsim'
94
+ ) {
95
+ return 'embedded rnx'
96
+ }
97
+ return null
98
+ } catch {
99
+ return null
100
+ }
101
+ }
102
+
103
+ function printConnectedSims(sims: BridgeSimInfo[], selectedId?: string) {
104
+ if (sims.length === 0) {
105
+ console.log(' no sims connected')
106
+ return
107
+ }
108
+ console.log(` connected sims (${sims.length}):\n`)
109
+ for (const sim of sims) {
110
+ const lockTag =
111
+ sim.lockedBy && sim.lockExpiresAt
112
+ ? `locked by ${formatLockOwner(sim.lockedBy)} (${Math.max(0, Math.round((sim.lockExpiresAt - Date.now()) / 1000))}s)`
113
+ : ''
114
+ const tags = [
115
+ sim.isPrimary ? 'primary' : '',
116
+ sim.id === selectedId ? 'selected' : '',
117
+ sim.readyState,
118
+ sim.userVisible === false ? 'hidden' : sim.userVisible === true ? 'visible' : '',
119
+ sim.attachedCliCount && sim.attachedCliCount > 0 ? 'in use' : '',
120
+ sim.userFocused ? 'focused' : '',
121
+ lockTag,
122
+ ].filter(Boolean)
123
+ console.log(` ${sim.id}${tags.length ? ` [${tags.join(', ')}]` : ''}`)
124
+ const loaded = summarizeSimUrl(sim.url)
125
+ if (loaded) {
126
+ console.log(` loaded: ${loaded}`)
127
+ } else if (sim.url) {
128
+ // no friendly summary — show the raw url so the caller can still copy it.
129
+ // truncate aggressively so a 400-char metro url doesn't dominate the list.
130
+ const shown = sim.url.length > 96 ? `${sim.url.slice(0, 93)}…` : sim.url
131
+ console.log(` url: ${shown}`)
132
+ } else if (sim.origin) {
133
+ console.log(` origin: ${sim.origin}`)
134
+ }
135
+ const engine = describeEngineSource(sim)
136
+ if (engine) console.log(` engine: ${engine}`)
137
+ if (sim.title) console.log(` title: ${sim.title}`)
138
+ if (sim.visibilityState && sim.visibilityState !== 'visible') {
139
+ console.log(` visibility: ${sim.visibilityState}`)
140
+ }
141
+ console.log(` connected: ${formatRelativeAge(Date.now() - sim.connectedAt)}`)
142
+ if (sim.lastActiveAt && sim.lastActiveAt > 0) {
143
+ console.log(` last active: ${formatRelativeAge(Date.now() - sim.lastActiveAt)}`)
144
+ }
145
+ }
146
+ }
147
+
148
+ // classify which engine a sim is running so dev-vs-published is never ambiguous
149
+ // to agents or humans reading `rnx list`. dev shell serves local source on
150
+ // :5173; the Contrast app embeds rnx on :3000; the bridge/daemon and the
151
+ // /__soot plugin serve the active runtime dir — which, inside a dev checkout, is
152
+ // the fresh dev-stack build (resolveDevCheckoutRuntimeRoot mode boundary), not a
153
+ // published runtime. keep this label honest so it never says "published" while
154
+ // actually serving local build output.
155
+ function describeEngineSource(sim: { url?: string; origin?: string }): string | null {
156
+ const ref = sim.url || sim.origin || ''
157
+ if (!ref) return null
158
+ try {
159
+ const port = new URL(ref).port
160
+ if (port === '5173') return 'dev — local source (:5173)'
161
+ if (port === '3000') return 'Contrast app embedded (:3000)'
162
+ if (resolveDevCheckoutRuntimeRoot()) return `dev — local build (:${port || '?'})`
163
+ return `runtime — published engine (:${port || '?'})`
164
+ } catch {
165
+ return null
166
+ }
167
+ }
168
+
169
+ function formatRelativeAge(ms: number): string {
170
+ const sec = Math.max(0, Math.round(ms / 1000))
171
+ if (sec < 60) return `${sec}s ago`
172
+ if (sec < 3600) return `${Math.round(sec / 60)}m ago`
173
+ if (sec < 86400) return `${Math.round(sec / 3600)}h ago`
174
+ return `${Math.round(sec / 86400)}d ago`
175
+ }