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,266 @@
1
+ // rnx camera drives the host-owned camera source. fixture video uses the
2
+ // same hidden video, canvas frame pump, ImageBitmap transfer, and
3
+ // BarcodeDetector loop as a real device camera. `do scan` remains the narrow
4
+ // synthetic special case for callers that have a payload string but no media.
5
+
6
+ import { createHash, randomUUID } from 'node:crypto'
7
+ import { createReadStream } from 'node:fs'
8
+ import fs from 'node:fs/promises'
9
+ import path from 'node:path'
10
+ import { cameraFixturesDir } from '../../src/home-paths'
11
+ import { printBridgeFailureDiagnostics } from '../bridge-diagnostics'
12
+ import {
13
+ createBridgeFromParsed,
14
+ evalInBridge,
15
+ parseBridgeCliArgs,
16
+ type WsBridge,
17
+ } from '../ws-bridge'
18
+ import type { CameraCommand, CameraResult } from '../../src/bridge-contract'
19
+
20
+ const CAMERA_FIXTURE_ROUTE = '/__camera-fixtures/'
21
+ const VIDEO_EXTENSIONS = new Set(['.m4v', '.mov', '.mp4', '.webm'])
22
+
23
+ function printHelp() {
24
+ console.log(`
25
+ rnx camera — control the simulator camera source
26
+
27
+ usage:
28
+ rnx camera play <file> [--loop] [--rate <number>]
29
+ rnx camera stop
30
+ rnx camera status [--json]
31
+ rnx do scan <text> [--format <web-format>]
32
+
33
+ subcommands:
34
+ play <file> replace the active camera source with a local video fixture
35
+ stop clear fixture mode and restore the live camera source
36
+ status report live, fixture, or idle source state
37
+ scan <text> inject a synthetic barcode result into an active scan session
38
+
39
+ options:
40
+ --loop loop fixture playback
41
+ --rate <n> fixture playback rate (default: 1)
42
+ --format <f> synthetic BarcodeDetector format (default: qr_code)
43
+ --json print status/result as JSON
44
+ --sim <id> target a specific sim
45
+ --port <p> WS bridge port (default: 7668)
46
+
47
+ examples:
48
+ rnx camera play ./fixtures/qr.mp4 --loop
49
+ rnx camera play ./fixtures/document.webm --rate 2
50
+ rnx camera status
51
+ rnx camera stop
52
+ rnx do scan "https://example.com" --format qr_code
53
+ `)
54
+ }
55
+
56
+ function isCameraResult(value: unknown): value is CameraResult {
57
+ if (!value || typeof value !== 'object') return false
58
+ const ok = Reflect.get(value, 'ok')
59
+ const source = Reflect.get(value, 'source')
60
+ return (
61
+ typeof ok === 'boolean' &&
62
+ (source === 'live' || source === 'fixture' || source === 'idle')
63
+ )
64
+ }
65
+
66
+ async function sendCameraCommand(
67
+ bridge: WsBridge,
68
+ camera: CameraCommand,
69
+ ): Promise<CameraResult> {
70
+ const result: unknown = await bridge.send({ type: 'camera', camera })
71
+ if (!isCameraResult(result)) {
72
+ throw new Error('camera bridge returned an invalid result')
73
+ }
74
+ return result
75
+ }
76
+
77
+ function printCameraResult(result: CameraResult, json: boolean): void {
78
+ if (json) {
79
+ console.log(JSON.stringify(result))
80
+ return
81
+ }
82
+ if (result.source === 'fixture') {
83
+ console.log(
84
+ ` camera source: fixture (${result.url ?? 'unknown url'}, ${
85
+ result.loop ? 'looping' : 'once'
86
+ }, ${result.rate ?? 1}x)`,
87
+ )
88
+ } else if (result.source === 'live') {
89
+ console.log(' camera source: live')
90
+ } else if (result.url) {
91
+ console.log(
92
+ ` camera source: idle (fixture queued: ${result.url}, ${
93
+ result.loop ? 'looping' : 'once'
94
+ }, ${result.rate ?? 1}x)`,
95
+ )
96
+ } else {
97
+ console.log(' camera source: idle')
98
+ }
99
+ if (!result.ok && result.error) console.error(` camera failed: ${result.error}`)
100
+ }
101
+
102
+ export async function runCamera(
103
+ args: string[],
104
+ opts: { port?: number } = {},
105
+ ): Promise<number> {
106
+ const parsed = parseBridgeCliArgs(args, {
107
+ port: opts.port,
108
+ stripBooleanFlags: ['--help', '-h', '--json', '--loop'],
109
+ stripValueFlags: ['--format', '--rate'],
110
+ })
111
+ const positional = parsed.positional
112
+ const subcommand = positional[0]
113
+ if (
114
+ !subcommand ||
115
+ subcommand === 'help' ||
116
+ args.includes('--help') ||
117
+ args.includes('-h')
118
+ ) {
119
+ printHelp()
120
+ return subcommand ? 0 : 1
121
+ }
122
+
123
+ const bridge = createBridgeFromParsed(parsed)
124
+ const json = args.includes('--json')
125
+ try {
126
+ if (subcommand === 'scan') {
127
+ const text = positional[1]
128
+ if (!text) {
129
+ console.error(' usage: rnx do scan <text>')
130
+ return 1
131
+ }
132
+ const formatFlag = args.indexOf('--format')
133
+ const format = formatFlag >= 0 ? args[formatFlag + 1] : 'qr_code'
134
+ if (!format) {
135
+ console.error(' --format requires a value')
136
+ return 1
137
+ }
138
+ const injected = await evalInBridge<boolean>(
139
+ bridge,
140
+ `globalThis.__sootsimCameraInjectScan?.(${JSON.stringify(text)}, ${JSON.stringify(format)}) ?? false`,
141
+ )
142
+ if (!injected) {
143
+ console.error(
144
+ ' no active barcode scan session. open a scanning <CameraView> first',
145
+ )
146
+ return 1
147
+ }
148
+ console.log(` injected ${format} scan: ${text}`)
149
+ return 0
150
+ }
151
+
152
+ if (subcommand === 'status') {
153
+ const result = await sendCameraCommand(bridge, { action: 'status' })
154
+ printCameraResult(result, json)
155
+ return result.ok ? 0 : 1
156
+ }
157
+
158
+ if (subcommand === 'stop') {
159
+ const result = await sendCameraCommand(bridge, { action: 'stop' })
160
+ printCameraResult(result, json)
161
+ return result.ok ? 0 : 1
162
+ }
163
+
164
+ if (subcommand === 'play') {
165
+ const input = positional[1]
166
+ if (!input) {
167
+ console.error(' usage: rnx camera play <file> [--loop] [--rate <number>]')
168
+ return 1
169
+ }
170
+ const sourcePath = path.resolve(input)
171
+ const extension = path.extname(sourcePath).toLowerCase()
172
+ if (!VIDEO_EXTENSIONS.has(extension)) {
173
+ console.error(
174
+ ` unsupported camera fixture extension ${extension || '(none)'}. use mp4, webm, mov, or m4v`,
175
+ )
176
+ return 1
177
+ }
178
+ let sourceStat
179
+ try {
180
+ sourceStat = await fs.stat(sourcePath)
181
+ } catch {
182
+ console.error(` camera fixture not found: ${sourcePath}`)
183
+ return 1
184
+ }
185
+ if (!sourceStat.isFile() || sourceStat.size === 0) {
186
+ console.error(` camera fixture must be a non-empty file: ${sourcePath}`)
187
+ return 1
188
+ }
189
+
190
+ const rateFlag = args.indexOf('--rate')
191
+ const rawRate = rateFlag >= 0 ? args[rateFlag + 1] : undefined
192
+ const rate = rawRate === undefined ? 1 : Number(rawRate)
193
+ if (!Number.isFinite(rate) || rate <= 0) {
194
+ console.error(' --rate must be a positive finite number')
195
+ return 1
196
+ }
197
+
198
+ const fixtureDirectory = cameraFixturesDir()
199
+ await fs.mkdir(fixtureDirectory, { recursive: true })
200
+ const digest = createHash('sha256')
201
+ await new Promise<void>((resolve, reject) => {
202
+ const input = createReadStream(sourcePath)
203
+ input.on('data', (chunk) => digest.update(chunk))
204
+ input.once('error', reject)
205
+ input.once('end', resolve)
206
+ })
207
+ const filename = `${digest.digest('hex')}${extension}`
208
+ const servedPath = path.join(fixtureDirectory, filename)
209
+ let staged = false
210
+ try {
211
+ const servedStat = await fs.lstat(servedPath)
212
+ if (!servedStat.isFile()) {
213
+ throw new Error(`camera fixture cache entry is not a file: ${servedPath}`)
214
+ }
215
+ staged = true
216
+ } catch (error) {
217
+ if (
218
+ !error ||
219
+ typeof error !== 'object' ||
220
+ Reflect.get(error, 'code') !== 'ENOENT'
221
+ ) {
222
+ throw error
223
+ }
224
+ }
225
+ if (!staged) {
226
+ const temporaryPath = path.join(
227
+ fixtureDirectory,
228
+ `.${filename}.${process.pid}-${randomUUID()}.tmp`,
229
+ )
230
+ await fs.copyFile(sourcePath, temporaryPath)
231
+ try {
232
+ await fs.link(temporaryPath, servedPath)
233
+ } catch (error) {
234
+ if (
235
+ !error ||
236
+ typeof error !== 'object' ||
237
+ Reflect.get(error, 'code') !== 'EEXIST'
238
+ ) {
239
+ throw error
240
+ }
241
+ } finally {
242
+ await fs.unlink(temporaryPath).catch(() => {})
243
+ }
244
+ }
245
+ const url = `http://127.0.0.1:${parsed.wsPort}${CAMERA_FIXTURE_ROUTE}${filename}`
246
+ const result = await sendCameraCommand(bridge, {
247
+ action: 'play',
248
+ url,
249
+ loop: args.includes('--loop'),
250
+ rate,
251
+ })
252
+ printCameraResult(result, json)
253
+ return result.ok ? 0 : 1
254
+ }
255
+
256
+ console.error(` unknown camera subcommand: ${subcommand}`)
257
+ printHelp()
258
+ return 1
259
+ } catch (error) {
260
+ console.error(` camera failed: ${error instanceof Error ? error.message : error}`)
261
+ await printBridgeFailureDiagnostics(bridge)
262
+ return 1
263
+ } finally {
264
+ bridge.close()
265
+ }
266
+ }
@@ -0,0 +1,169 @@
1
+ import {
2
+ applyAutomaticSootsimCleanup,
3
+ applySootsimCleanup,
4
+ formatSootsimBytes,
5
+ planSootsimCleanup,
6
+ type SootsimCleanupEntry,
7
+ type SootsimCleanupKind,
8
+ } from '../../src/disk-cleanup'
9
+
10
+ const KIND_LABELS: Record<SootsimCleanupKind, string> = {
11
+ 'browser-cache': 'browser caches',
12
+ 'camera-fixture': 'staged camera fixture videos',
13
+ recording: 'recordings and captured frames',
14
+ 'runtime-archive': 'downloaded runtime archives',
15
+ 'runtime-version': 'inactive runtime versions',
16
+ }
17
+
18
+ function summarize(entries: SootsimCleanupEntry[]): string[] {
19
+ const byKind = new Map<SootsimCleanupKind, { bytes: number; count: number }>()
20
+ for (const entry of entries) {
21
+ const current = byKind.get(entry.kind) ?? { bytes: 0, count: 0 }
22
+ current.bytes += entry.bytes
23
+ current.count++
24
+ byKind.set(entry.kind, current)
25
+ }
26
+ return [...byKind]
27
+ .sort((left, right) => right[1].bytes - left[1].bytes)
28
+ .map(
29
+ ([kind, summary]) =>
30
+ ` ${KIND_LABELS[kind]}: ${formatSootsimBytes(summary.bytes)} (${summary.count} ${
31
+ summary.count === 1 ? 'item' : 'items'
32
+ })`,
33
+ )
34
+ }
35
+
36
+ function printHelp(): void {
37
+ console.log(`
38
+ rnx cleanup — inspect and reclaim local rnx disk usage
39
+
40
+ usage:
41
+ rnx cleanup
42
+ rnx cleanup --apply
43
+ rnx cleanup --aggressive
44
+ rnx cleanup --apply --aggressive
45
+
46
+ The default is a read-only preview. --apply removes downloaded runtime archives,
47
+ old runtimes outside a two-version rollback window, and disposable browser
48
+ caches. It never removes the active runtime, a runtime serving a connected
49
+ simulator, cookies, localStorage, IndexedDB, service workers, or other persistent
50
+ app storage.
51
+
52
+ --aggressive additionally removes the rollback runtime plus user-created
53
+ recordings and captured frames. It still preserves the active runtime and app storage.
54
+ `)
55
+ }
56
+
57
+ export async function runCleanup(args: string[]): Promise<number> {
58
+ if (args.length === 1 && args[0] === '--automatic-worker') {
59
+ try {
60
+ const result = applyAutomaticSootsimCleanup()
61
+ if (result.status === 'complete') {
62
+ process.stderr.write(
63
+ result.reclaimedBytes > 0
64
+ ? ` rnx automatic cleanup reclaimed ${formatSootsimBytes(
65
+ result.reclaimedBytes,
66
+ )}; active runtimes and app data were kept.\n`
67
+ : ' rnx automatic cleanup found no obsolete local data.\n',
68
+ )
69
+ } else if (result.status === 'deferred') {
70
+ process.stderr.write(
71
+ ` rnx automatic cleanup reclaimed ${formatSootsimBytes(
72
+ result.reclaimedBytes,
73
+ )}; ${formatSootsimBytes(
74
+ result.deferredInUseBytes,
75
+ )} is in use and will be retried on a later run.\n`,
76
+ )
77
+ }
78
+ return 0
79
+ } catch (error) {
80
+ process.stderr.write(
81
+ ` rnx automatic cleanup will retry on the next run: ${
82
+ error instanceof Error ? error.message : String(error)
83
+ }\n`,
84
+ )
85
+ return 0
86
+ }
87
+ }
88
+ if (args.includes('--help') || args.includes('-h')) {
89
+ printHelp()
90
+ return 0
91
+ }
92
+ const known = new Set(['--aggressive', '--apply'])
93
+ const unknown = args.find((arg) => !known.has(arg))
94
+ if (unknown) {
95
+ console.error(` cleanup: unknown option ${unknown}`)
96
+ printHelp()
97
+ return 1
98
+ }
99
+
100
+ const aggressive = args.includes('--aggressive')
101
+ const apply = args.includes('--apply')
102
+ const plan = planSootsimCleanup({ aggressive })
103
+ const selected = plan.entries.filter((entry) => entry.selected)
104
+ const safe = selected.filter((entry) => entry.state === 'safe')
105
+ const aggressiveOnly = plan.entries.filter(
106
+ (entry) => entry.state === 'aggressive' && entry.selected,
107
+ )
108
+ const inUse = plan.entries.filter((entry) => entry.state === 'in-use')
109
+ const inUseBrowserCaches = inUse.filter((entry) => entry.kind === 'browser-cache')
110
+ const inUseRuntimes = inUse.filter((entry) => entry.kind === 'runtime-version')
111
+ const rollback = plan.entries.find(
112
+ (entry) => entry.kind === 'runtime-version' && entry.state === 'aggressive',
113
+ )
114
+
115
+ console.log(` rnx data home: ${plan.home}`)
116
+ console.log(` current size: ${formatSootsimBytes(plan.totalBytes)}`)
117
+ console.log(` active runtime: ${plan.activeRuntime ?? '(none installed)'}`)
118
+ console.log('')
119
+ console.log(apply ? ' safe items selected for removal:' : ' safe to reclaim:')
120
+ for (const line of summarize(safe)) console.log(line)
121
+ if (safe.length === 0) console.log(' nothing')
122
+
123
+ if (aggressiveOnly.length > 0) {
124
+ console.log('')
125
+ console.log(' aggressive additions:')
126
+ for (const line of summarize(aggressiveOnly)) console.log(line)
127
+ }
128
+
129
+ console.log('')
130
+ console.log(' protected:')
131
+ if (plan.activeRuntime) console.log(` active runtime ${plan.activeRuntime} (in use)`)
132
+ for (const entry of inUseRuntimes) {
133
+ console.log(` running daemon runtime ${entry.path}`)
134
+ }
135
+ if (rollback && !aggressive) {
136
+ console.log(` rollback runtime ${rollback.path}`)
137
+ }
138
+ console.log(' cookies, localStorage, IndexedDB, service workers, and profile data')
139
+ if (!aggressive) {
140
+ console.log(' recordings and captured frames (use --aggressive to remove)')
141
+ }
142
+ if (inUseBrowserCaches.length > 0) {
143
+ console.log(
144
+ ` ${formatSootsimBytes(
145
+ inUseBrowserCaches.reduce((total, entry) => total + entry.bytes, 0),
146
+ )} ` + `of browser cache is open; close rnx and rerun cleanup`,
147
+ )
148
+ }
149
+
150
+ console.log('')
151
+ console.log(` reclaimable now: ${formatSootsimBytes(plan.reclaimableBytes)}`)
152
+ if (!apply) {
153
+ console.log(
154
+ ` preview only — run \`rnx cleanup --apply${
155
+ aggressive ? ' --aggressive' : ''
156
+ }\` to remove these items`,
157
+ )
158
+ return 0
159
+ }
160
+
161
+ const result = applySootsimCleanup({ aggressive })
162
+ console.log(
163
+ ` removed ${result.removedPaths.length} ${
164
+ result.removedPaths.length === 1 ? 'item' : 'items'
165
+ }; reclaimed ${formatSootsimBytes(result.reclaimedBytes)}`,
166
+ )
167
+ console.log(` remaining rnx data: ${formatSootsimBytes(result.remainingBytes)}`)
168
+ return 0
169
+ }
@@ -0,0 +1,176 @@
1
+ // rnx compat — scan dependencies with the shared compatibility engine.
2
+
3
+ import { existsSync, readFileSync } from 'node:fs'
4
+ import { dirname, join, relative, resolve } from 'node:path'
5
+ import {
6
+ COMPAT_RESULT_CAVEAT,
7
+ formatDetailedScanLines,
8
+ formatScanCounts,
9
+ scanDeps,
10
+ type ScanResult,
11
+ } from '@sootsim/compat/web'
12
+ import { findProjectRoot, scanWorkspace } from './detect'
13
+
14
+ interface PackageManifest {
15
+ name?: string
16
+ dependencies?: Record<string, string>
17
+ devDependencies?: Record<string, string>
18
+ }
19
+
20
+ export async function runCompat(args: string[]): Promise<void> {
21
+ if (args.includes('--help') || args.includes('-h')) {
22
+ console.log(`
23
+ rnx compat — check package compatibility
24
+
25
+ usage:
26
+ rnx compat [package-name]
27
+ rnx compat --json
28
+ rnx compat --brief
29
+
30
+ flags:
31
+ --json machine-readable output
32
+ --brief one-line summary
33
+ --app <dir> target directory (default: cwd)
34
+ `)
35
+ return
36
+ }
37
+
38
+ const appIndex = args.indexOf('--app')
39
+ const appDir =
40
+ appIndex >= 0 && args[appIndex + 1] ? resolve(args[appIndex + 1]) : process.cwd()
41
+ const packageName = args.find(
42
+ (arg, index) => !arg.startsWith('-') && (appIndex < 0 || index !== appIndex + 1),
43
+ )
44
+ const pkg = readPackageJson(appDir)
45
+ if (!pkg) {
46
+ console.error(` no package.json found in ${appDir}`)
47
+ process.exitCode = 1
48
+ return
49
+ }
50
+
51
+ if (packageName) {
52
+ report(args, scanOnePackage(appDir, pkg, packageName))
53
+ return
54
+ }
55
+
56
+ const here = scanDeps(installedManifest(appDir, pkg))
57
+ if (here.packages.length > 0 || appIndex >= 0) {
58
+ report(args, here)
59
+ return
60
+ }
61
+
62
+ // a monorepo root manifest is not the app. it carries react-native and expo
63
+ // so the workspace resolves and little else, so scanning it reports a
64
+ // project with nothing to check while the real app sits in a subdirectory.
65
+ const workspaceRoot = findProjectRoot(appDir)
66
+ const elsewhere = scanWorkspace(workspaceRoot).apps.flatMap((app) => {
67
+ if (resolve(app.dir) === appDir) return []
68
+ const manifest = readPackageJson(app.dir)
69
+ if (!manifest) return []
70
+ const result = scanDeps(installedManifest(app.dir, manifest))
71
+ return result.packages.length > 0 ? [{ dir: app.dir, result }] : []
72
+ })
73
+
74
+ if (elsewhere.length === 1) {
75
+ const only = elsewhere[0]
76
+ console.log(`\n scanning ${relative(appDir, only.dir) || '.'}`)
77
+ report(args, only.result)
78
+ return
79
+ }
80
+ if (elsewhere.length > 1) {
81
+ console.log(`\n this workspace has more than one app. scan one with --app:`)
82
+ for (const entry of elsewhere) {
83
+ console.log(` rnx compat --app ${relative(appDir, entry.dir)}`)
84
+ }
85
+ console.log()
86
+ return
87
+ }
88
+ report(args, here)
89
+ }
90
+
91
+ function report(args: string[], result: ScanResult): void {
92
+ if (args.includes('--json')) {
93
+ console.log(JSON.stringify(result, null, 2))
94
+ return
95
+ }
96
+ if (args.includes('--brief')) {
97
+ console.log(` ${formatScanCounts(result)}`)
98
+ console.log(` ${COMPAT_RESULT_CAVEAT}`)
99
+ return
100
+ }
101
+
102
+ console.log(`\n rnx compatibility report`)
103
+ console.log(` ${formatScanCounts(result)}\n`)
104
+ for (const line of formatDetailedScanLines(result)) console.log(` ${line}`)
105
+ const supported = result.packages.filter((item) => item.status === 'full').length
106
+ if (supported > 0) console.log(` supported: ${supported} packages`)
107
+ const notRelevant = result.packages.filter(
108
+ (item) => item.status === 'not-relevant',
109
+ ).length
110
+ if (notRelevant > 0) console.log(` not relevant at runtime: ${notRelevant} packages`)
111
+ if (result.packages.length === 0) {
112
+ console.log(` no React Native ecosystem packages detected`)
113
+ }
114
+ console.log(`\n ${COMPAT_RESULT_CAVEAT}\n`)
115
+ }
116
+
117
+ // the declared range is often not a version: pnpm and bun catalogs write
118
+ // `catalog:`, a workspace dependency writes `workspace:*`, and a pinned tarball
119
+ // writes a URL. none of those match a verified version range, so a whole app
120
+ // reports as unverified. the installed tree holds the version that will
121
+ // actually run, so ask it, and keep the declared range for anything the project
122
+ // has not installed.
123
+ function installedManifest(appDir: string, pkg: PackageManifest): PackageManifest {
124
+ const resolveVersion = (name: string, declared: string): string => {
125
+ let dir = appDir
126
+ while (true) {
127
+ const manifest = join(dir, 'node_modules', name, 'package.json')
128
+ if (existsSync(manifest)) {
129
+ try {
130
+ const parsed: unknown = JSON.parse(readFileSync(manifest, 'utf8'))
131
+ if (parsed && typeof parsed === 'object' && 'version' in parsed) {
132
+ const version = parsed.version
133
+ if (typeof version === 'string') return version
134
+ }
135
+ } catch {}
136
+ return declared
137
+ }
138
+ const parent = dirname(dir)
139
+ if (parent === dir) return declared
140
+ dir = parent
141
+ }
142
+ }
143
+ const mapped = (deps: Record<string, string> | undefined) =>
144
+ deps &&
145
+ Object.fromEntries(
146
+ Object.entries(deps).map(([name, declared]) => [
147
+ name,
148
+ resolveVersion(name, declared),
149
+ ]),
150
+ )
151
+ return {
152
+ name: pkg.name,
153
+ dependencies: mapped(pkg.dependencies),
154
+ devDependencies: mapped(pkg.devDependencies),
155
+ }
156
+ }
157
+
158
+ function readPackageJson(appDir: string): PackageManifest | null {
159
+ const packagePath = resolve(appDir, 'package.json')
160
+ if (!existsSync(packagePath)) return null
161
+ const parsed: unknown = JSON.parse(readFileSync(packagePath, 'utf8'))
162
+ return parsed && typeof parsed === 'object' ? parsed : null
163
+ }
164
+
165
+ function scanOnePackage(
166
+ appDir: string,
167
+ pkg: PackageManifest,
168
+ packageName: string,
169
+ ): ScanResult {
170
+ const installed = installedManifest(appDir, pkg)
171
+ const version =
172
+ installed.dependencies?.[packageName] ??
173
+ installed.devDependencies?.[packageName] ??
174
+ '*'
175
+ return scanDeps({ dependencies: { [packageName]: version } })
176
+ }
@@ -0,0 +1,34 @@
1
+ import { rnxExit } from '../run-rnx'
2
+ import { runTelemetry } from './telemetry'
3
+
4
+ function printHelp(): void {
5
+ console.log(`
6
+ rnx config — manage local CLI preferences
7
+
8
+ usage:
9
+ rnx config telemetry [on|off|status]
10
+
11
+ environment:
12
+ RNX_HINTS=off suppress CLI hints
13
+ RNX_HINTS=always show every CLI hint every time
14
+ RNX_UPDATE_CHECK=off disable background CLI update checks and notices
15
+ `)
16
+ }
17
+
18
+ export async function runConfig(args: string[]): Promise<void> {
19
+ if (args.includes('--help') || args.includes('-h')) {
20
+ printHelp()
21
+ return
22
+ }
23
+
24
+ const sub = args.find((arg) => !arg.startsWith('-'))
25
+ if (sub === 'telemetry') {
26
+ const index = args.indexOf(sub)
27
+ await runTelemetry([...args.slice(0, index), ...args.slice(index + 1)])
28
+ return
29
+ }
30
+
31
+ console.error(` unknown config command: ${sub ?? '(missing)'}`)
32
+ printHelp()
33
+ rnxExit(1)
34
+ }