rnxsim 0.1.381 → 0.1.382

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 (138) hide show
  1. package/README.md +19 -6
  2. package/cli/app-fonts.ts +1 -1
  3. package/cli/bin.ts +118 -26
  4. package/cli/bridge-diagnostics.ts +1 -1
  5. package/cli/bridge-flow-runner.ts +9 -9
  6. package/cli/browser-evals.ts +3 -3
  7. package/cli/cli-update.ts +261 -0
  8. package/cli/cloud-client.ts +407 -0
  9. package/cli/cloud-dispatch.ts +165 -0
  10. package/cli/cloud-session.ts +156 -0
  11. package/cli/command-contract.ts +2 -2
  12. package/cli/command-registry.ts +9 -0
  13. package/cli/commands/agent-wrapper.ts +4 -4
  14. package/cli/commands/agent.ts +3 -3
  15. package/cli/commands/box/checkout-plane.ts +16 -8
  16. package/cli/commands/box/rnx-command.ts +2 -2
  17. package/cli/commands/box/search-command.ts +2 -2
  18. package/cli/commands/box.ts +123 -70
  19. package/cli/commands/config.ts +3 -2
  20. package/cli/commands/control.ts +6 -6
  21. package/cli/commands/cpu-profile.ts +2 -2
  22. package/cli/commands/daemon.ts +7 -7
  23. package/cli/commands/debug.ts +2 -2
  24. package/cli/commands/detox.ts +7 -7
  25. package/cli/commands/diagnose.ts +1 -1
  26. package/cli/commands/electron.ts +1 -1
  27. package/cli/commands/film.ts +1 -1
  28. package/cli/commands/flow.ts +4 -4
  29. package/cli/commands/inspect/core.ts +58 -7
  30. package/cli/commands/inspect/env.ts +5 -3
  31. package/cli/commands/inspect/shared.ts +2 -1
  32. package/cli/commands/inspect/wait-event.ts +1 -1
  33. package/cli/commands/inspect/wait-ready.ts +15 -5
  34. package/cli/commands/inspect.ts +31 -9
  35. package/cli/commands/install-desktop.ts +2 -2
  36. package/cli/commands/maestro.ts +3 -3
  37. package/cli/commands/permissions.ts +1 -1
  38. package/cli/commands/platform.ts +91 -0
  39. package/cli/commands/record.ts +3 -3
  40. package/cli/commands/report-issue.ts +1 -1
  41. package/cli/commands/runtime.ts +1 -1
  42. package/cli/commands/screenshot-mode.ts +1 -1
  43. package/cli/commands/screenshot.ts +25 -0
  44. package/cli/commands/serve.ts +1 -1
  45. package/cli/commands/skills.ts +1 -1
  46. package/cli/commands/timeline.ts +1 -1
  47. package/cli/commands/upgrade.ts +8 -34
  48. package/cli/commands/upload.ts +3 -3
  49. package/cli/commands/version.ts +14 -3
  50. package/cli/commands/what-happened.ts +2 -2
  51. package/cli/current-sim.ts +43 -30
  52. package/cli/desktop-companion.ts +1 -1
  53. package/cli/drivers/electron.ts +1 -1
  54. package/cli/drivers/playwright.ts +3 -3
  55. package/cli/drivers/types.ts +4 -4
  56. package/cli/flow-file.ts +3 -3
  57. package/cli/help-core.ts +18 -11
  58. package/cli/hidden-runtime-alias.ts +1 -1
  59. package/cli/hints.ts +2 -2
  60. package/cli/maestro-js.ts +2 -2
  61. package/cli/outbound-endpoints.ts +40 -6
  62. package/cli/parse-args.ts +1 -1
  63. package/cli/registry.ts +1 -1
  64. package/cli/resolve-assets.ts +2 -2
  65. package/cli/secure-file.ts +31 -0
  66. package/cli/shell-init.ts +98 -0
  67. package/cli/ws-bridge.ts +18 -8
  68. package/detox/expectations.ts +1 -1
  69. package/detox/gestures.ts +3 -3
  70. package/detox/index.ts +3 -3
  71. package/dist-lib/agent-daemon-client.cjs +1 -1
  72. package/dist-lib/agent-events.cjs +1 -1
  73. package/dist-lib/agent-identity.cjs +1 -1
  74. package/dist-lib/agent-sessions.cjs +1 -1
  75. package/dist-lib/attached-projects.cjs +1 -1
  76. package/dist-lib/auth/shared-session.cjs +1 -1
  77. package/dist-lib/backend-origin.cjs +1 -1
  78. package/dist-lib/beta.cjs +1 -1
  79. package/dist-lib/beta.mjs +1 -1
  80. package/dist-lib/bridge-constants.cjs +1 -1
  81. package/dist-lib/bridge-contract-input.cjs +15 -8
  82. package/dist-lib/bridge-contract-input.mjs +15 -8
  83. package/dist-lib/bridge-contract.cjs +1 -1
  84. package/dist-lib/bridge-contract.mjs +1 -1
  85. package/dist-lib/capture-contract.cjs +107 -0
  86. package/dist-lib/capture-contract.mjs +80 -0
  87. package/dist-lib/cli-constants.cjs +1 -1
  88. package/dist-lib/cloud-contract.cjs +32 -0
  89. package/dist-lib/cloud-contract.mjs +7 -0
  90. package/dist-lib/config.cjs +1 -1
  91. package/dist-lib/detox/index.cjs +4 -4
  92. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  93. package/dist-lib/home-paths.cjs +9 -1
  94. package/dist-lib/host/bridge-host.cjs +15 -10
  95. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  96. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  97. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  98. package/dist-lib/host/replacement-module-handler.cjs +1 -1
  99. package/dist-lib/host/websocket-proxy.cjs +1 -1
  100. package/dist-lib/index.cjs +248 -73
  101. package/dist-lib/jump-to-source-babel.cjs +1 -1
  102. package/dist-lib/menu.cjs +1 -1
  103. package/dist-lib/menu.mjs +1 -1
  104. package/dist-lib/metro-production-bundle.cjs +1 -1
  105. package/dist-lib/metro-production-bundle.mjs +1 -1
  106. package/dist-lib/metro.cjs +1 -1
  107. package/dist-lib/profiles.cjs +1 -1
  108. package/dist-lib/public-brand.cjs +1 -1
  109. package/dist-lib/react-native-host-modules.cjs +1 -1
  110. package/dist-lib/react-native-host-modules.mjs +1 -1
  111. package/dist-lib/render-mode.cjs +1 -1
  112. package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
  113. package/dist-lib/sdk.cjs +44 -6
  114. package/dist-lib/sdk.mjs +44 -6
  115. package/dist-lib/skills.cjs +435 -155
  116. package/dist-lib/vite.cjs +1 -1
  117. package/package.json +20 -4
  118. package/scripts/dev-server-scanner.ts +3 -3
  119. package/skills/contrast/SKILL.md +1 -1
  120. package/skills/rnx-perf/SKILL.md +68 -52
  121. package/src/app-url.ts +2 -3
  122. package/src/bridge-contract-input.ts +17 -6
  123. package/src/bridge-contract.ts +25 -4
  124. package/src/capture-contract.ts +423 -0
  125. package/src/cloud-contract.ts +15 -0
  126. package/src/home-paths.ts +9 -3
  127. package/src/host/agent-host.ts +1 -1
  128. package/src/host/bridge-host.ts +13 -11
  129. package/src/host/plane-host.ts +2 -2
  130. package/src/index.ts +21 -0
  131. package/src/native-dev-bundle-url.ts +2 -2
  132. package/src/screenshots/orchestrate.ts +1 -1
  133. package/src/screenshots/org-deck.ts +1 -1
  134. package/src/settings-bundle.ts +1 -1
  135. package/src/sim-client.ts +4 -0
  136. package/src/vite-plugin-one.ts +2 -2
  137. package/src/vite-plugin.ts +1 -1
  138. package/cli/commands/box/cloud-box.ts +0 -294
@@ -0,0 +1,423 @@
1
+ // public, runtime-neutral screen capture contract. semantic evidence and pixel
2
+ // evidence are separate because neither one proves the other.
3
+
4
+ export const RNX_SCREEN_CAPTURE_VERSION = 1 as const
5
+
6
+ export interface RnxCaptureRect {
7
+ x: number
8
+ y: number
9
+ width: number
10
+ height: number
11
+ }
12
+
13
+ export interface RnxCaptureAccessibilityState {
14
+ disabled: boolean
15
+ selected: boolean
16
+ checked: boolean | 'mixed'
17
+ busy: boolean
18
+ expanded: boolean
19
+ }
20
+
21
+ export interface RnxCaptureAccessibilityValue {
22
+ min?: number
23
+ max?: number
24
+ now?: number
25
+ text?: string
26
+ }
27
+
28
+ export interface RnxCaptureAccessibility {
29
+ accessible: boolean
30
+ label: string | null
31
+ role: string | null
32
+ hint: string | null
33
+ liveRegion: 'none' | 'polite' | 'assertive' | null
34
+ state: RnxCaptureAccessibilityState | null
35
+ value: RnxCaptureAccessibilityValue | null
36
+ }
37
+
38
+ export interface RnxCaptureInteractionState {
39
+ pressed: boolean
40
+ pressable: boolean
41
+ textInput: boolean
42
+ textSelection: { start: number | null; end: number | null } | null
43
+ secureTextEntry: boolean
44
+ placeholder: string | null
45
+ scrollOffset: { x: number; y: number }
46
+ zoomScale: number | null
47
+ nativePinchEnabled: boolean
48
+ contentOffsetProp: { x?: number; y?: number } | null
49
+ decelerationRateProp: unknown
50
+ strokeDashoffsetProp: number | null
51
+ }
52
+
53
+ export interface RnxCaptureStyleTokenBinding {
54
+ token: string
55
+ theme: string
56
+ }
57
+
58
+ export interface RnxCaptureTextSegment {
59
+ text: string
60
+ color?: string | number
61
+ fontFamily?: string
62
+ fontSize?: number
63
+ fontStyle?: string
64
+ fontWeight?: string | number
65
+ letterSpacing?: number
66
+ textDecorationLine?: string
67
+ }
68
+
69
+ export interface RnxCaptureTextLine {
70
+ text: string
71
+ x: number
72
+ y: number
73
+ width: number
74
+ height: number
75
+ baseline: number
76
+ segments?: RnxCaptureTextSegment[]
77
+ }
78
+
79
+ export interface RnxCaptureSource {
80
+ location?: string
81
+ file?: string
82
+ componentName?: string
83
+ componentStack?: string[]
84
+ react?: {
85
+ component?: string
86
+ owners?: string[]
87
+ source?: string
88
+ key?: string
89
+ hasRef?: boolean
90
+ hooks?: {
91
+ total: number
92
+ counts: Record<string, number>
93
+ }
94
+ contexts?: string[]
95
+ lastCommit?: {
96
+ ageMs: number
97
+ causes: string[]
98
+ propDiffs?: string[]
99
+ stateChanged?: boolean
100
+ actualDurationMs?: number
101
+ }
102
+ }
103
+ }
104
+
105
+ export interface RnxCaptureNode {
106
+ nodeId: number
107
+ parentNodeId: number | null
108
+ childNodeIds: number[]
109
+ paintIndex: number
110
+ type: string
111
+ id: string | null
112
+ testID: string | null
113
+ text: string | null
114
+ childCount: number
115
+ accessibility: RnxCaptureAccessibility
116
+ interaction: RnxCaptureInteractionState
117
+ layout: {
118
+ local: RnxCaptureRect
119
+ frame: RnxCaptureRect | null
120
+ absolutePosition: { x: number; y: number }
121
+ visibleFrame: RnxCaptureRect | null
122
+ }
123
+ style: {
124
+ resolved: Record<string, unknown>
125
+ authored: Record<string, unknown>
126
+ computed: Record<string, unknown>
127
+ tokenBindings: Record<string, RnxCaptureStyleTokenBinding>
128
+ }
129
+ source: RnxCaptureSource
130
+ props: Record<string, unknown>
131
+ textLines?: RnxCaptureTextLine[]
132
+ /** additional generic inspect fields preserved for tooling adapters */
133
+ inspection: Record<string, unknown>
134
+ }
135
+
136
+ export interface RnxCaptureTree {
137
+ viewport: { width: number; height: number }
138
+ rootNodeId: number
139
+ nodes: RnxCaptureNode[]
140
+ }
141
+
142
+ export interface RnxCaptureVisualEvidence {
143
+ kind: 'png'
144
+ /** names the runtime that produced these pixels without claiming visibility */
145
+ producer: 'headless-cpu-app-surface' | 'browser-compositor-app-surface'
146
+ dataUri: string
147
+ pixelWidth: number
148
+ pixelHeight: number
149
+ }
150
+
151
+ export interface RnxCaptureRegionRequest {
152
+ key: string
153
+ /** host node whose own subtree is rendered into this transparent atlas region */
154
+ nodeId: number
155
+ frame: RnxCaptureRect
156
+ }
157
+
158
+ export interface RnxCaptureAtlasRegion extends RnxCaptureRegionRequest {
159
+ /** source rectangle inside the packed atlas, in logical atlas coordinates */
160
+ source: RnxCaptureRect
161
+ }
162
+
163
+ export interface RnxCaptureRegionAtlas {
164
+ kind: 'png-atlas'
165
+ producer: 'headless-cpu-app-surface'
166
+ dataUri: string
167
+ /** logical atlas dimensions; the encoded PNG uses pixelWidth × pixelHeight */
168
+ width: number
169
+ height: number
170
+ pixelWidth: number
171
+ pixelHeight: number
172
+ regions: RnxCaptureAtlasRegion[]
173
+ }
174
+
175
+ export interface RnxCaptureFontRegistration {
176
+ url?: string
177
+ family: string
178
+ sourceLabel?: string
179
+ parsedFamily?: string | null
180
+ parsedPostScript?: string | null
181
+ fullName?: string | null
182
+ }
183
+
184
+ export interface RnxScreenCapture {
185
+ contractVersion: typeof RNX_SCREEN_CAPTURE_VERSION
186
+ capturedAt: number
187
+ tree: RnxCaptureTree
188
+ /** app fonts registered in the captured tenant, included only when requested */
189
+ fonts?: RnxCaptureFontRegistration[]
190
+ /** optional full-frame diagnostic evidence, never required by tree consumers */
191
+ visual?: RnxCaptureVisualEvidence
192
+ }
193
+
194
+ function captureRecord(value: unknown): value is Record<string, unknown> {
195
+ return value !== null && typeof value === 'object' && !Array.isArray(value)
196
+ }
197
+
198
+ function captureInteger(value: unknown): value is number {
199
+ return typeof value === 'number' && Number.isSafeInteger(value)
200
+ }
201
+
202
+ function captureNumber(value: unknown): value is number {
203
+ return typeof value === 'number' && Number.isFinite(value)
204
+ }
205
+
206
+ function captureNullableString(value: unknown): value is string | null {
207
+ return value === null || typeof value === 'string'
208
+ }
209
+
210
+ function captureRect(value: unknown): value is RnxCaptureRect {
211
+ return (
212
+ captureRecord(value) &&
213
+ captureNumber(value.x) &&
214
+ captureNumber(value.y) &&
215
+ captureNumber(value.width) &&
216
+ captureNumber(value.height)
217
+ )
218
+ }
219
+
220
+ function captureOptionalString(value: unknown): value is string | undefined {
221
+ return value === undefined || typeof value === 'string'
222
+ }
223
+
224
+ function captureOptionalNullableString(
225
+ value: unknown,
226
+ ): value is string | null | undefined {
227
+ return value === undefined || captureNullableString(value)
228
+ }
229
+
230
+ function captureFontRegistration(value: unknown): value is RnxCaptureFontRegistration {
231
+ return (
232
+ captureRecord(value) &&
233
+ typeof value.family === 'string' &&
234
+ value.family.length > 0 &&
235
+ captureOptionalString(value.url) &&
236
+ captureOptionalString(value.sourceLabel) &&
237
+ captureOptionalNullableString(value.parsedFamily) &&
238
+ captureOptionalNullableString(value.parsedPostScript) &&
239
+ captureOptionalNullableString(value.fullName)
240
+ )
241
+ }
242
+
243
+ function capturePoint(value: unknown): value is { x: number; y: number } {
244
+ return captureRecord(value) && captureNumber(value.x) && captureNumber(value.y)
245
+ }
246
+
247
+ function captureNode(value: unknown): value is RnxCaptureNode {
248
+ if (
249
+ !captureRecord(value) ||
250
+ !captureInteger(value.nodeId) ||
251
+ !(value.parentNodeId === null || captureInteger(value.parentNodeId)) ||
252
+ !Array.isArray(value.childNodeIds) ||
253
+ !value.childNodeIds.every(captureInteger) ||
254
+ !captureInteger(value.paintIndex) ||
255
+ typeof value.type !== 'string' ||
256
+ !captureNullableString(value.id) ||
257
+ !captureNullableString(value.testID) ||
258
+ !captureNullableString(value.text) ||
259
+ !captureInteger(value.childCount) ||
260
+ !captureRecord(value.accessibility) ||
261
+ !captureRecord(value.interaction) ||
262
+ !captureRecord(value.layout) ||
263
+ !captureRecord(value.style) ||
264
+ !captureRecord(value.source) ||
265
+ !captureRecord(value.props) ||
266
+ !captureRecord(value.inspection)
267
+ ) {
268
+ return false
269
+ }
270
+ const accessibility = value.accessibility
271
+ if (
272
+ typeof accessibility.accessible !== 'boolean' ||
273
+ !captureNullableString(accessibility.label) ||
274
+ !captureNullableString(accessibility.role) ||
275
+ !captureNullableString(accessibility.hint) ||
276
+ !(
277
+ accessibility.liveRegion === null ||
278
+ accessibility.liveRegion === 'none' ||
279
+ accessibility.liveRegion === 'polite' ||
280
+ accessibility.liveRegion === 'assertive'
281
+ ) ||
282
+ !(accessibility.state === null || captureRecord(accessibility.state)) ||
283
+ !(accessibility.value === null || captureRecord(accessibility.value))
284
+ ) {
285
+ return false
286
+ }
287
+ const interaction = value.interaction
288
+ if (
289
+ typeof interaction.pressed !== 'boolean' ||
290
+ typeof interaction.pressable !== 'boolean' ||
291
+ typeof interaction.textInput !== 'boolean' ||
292
+ !(interaction.textSelection === null || captureRecord(interaction.textSelection)) ||
293
+ typeof interaction.secureTextEntry !== 'boolean' ||
294
+ !captureNullableString(interaction.placeholder) ||
295
+ !capturePoint(interaction.scrollOffset) ||
296
+ !(interaction.zoomScale === null || captureNumber(interaction.zoomScale)) ||
297
+ typeof interaction.nativePinchEnabled !== 'boolean' ||
298
+ !(
299
+ interaction.contentOffsetProp === null ||
300
+ captureRecord(interaction.contentOffsetProp)
301
+ ) ||
302
+ !(
303
+ interaction.strokeDashoffsetProp === null ||
304
+ captureNumber(interaction.strokeDashoffsetProp)
305
+ )
306
+ ) {
307
+ return false
308
+ }
309
+ const layout = value.layout
310
+ if (
311
+ !captureRect(layout.local) ||
312
+ !(layout.frame === null || captureRect(layout.frame)) ||
313
+ !capturePoint(layout.absolutePosition) ||
314
+ !(layout.visibleFrame === null || captureRect(layout.visibleFrame))
315
+ ) {
316
+ return false
317
+ }
318
+ const style = value.style
319
+ return (
320
+ captureRecord(style.resolved) &&
321
+ captureRecord(style.authored) &&
322
+ captureRecord(style.computed) &&
323
+ captureRecord(style.tokenBindings) &&
324
+ (value.textLines === undefined || Array.isArray(value.textLines))
325
+ )
326
+ }
327
+
328
+ export function isRnxScreenCapture(value: unknown): value is RnxScreenCapture {
329
+ if (
330
+ !captureRecord(value) ||
331
+ value.contractVersion !== RNX_SCREEN_CAPTURE_VERSION ||
332
+ !captureInteger(value.capturedAt) ||
333
+ !captureRecord(value.tree) ||
334
+ !(
335
+ value.fonts === undefined ||
336
+ (Array.isArray(value.fonts) &&
337
+ value.fonts.length <= 200 &&
338
+ value.fonts.every(captureFontRegistration))
339
+ ) ||
340
+ !(value.visual === undefined || captureRecord(value.visual))
341
+ ) {
342
+ return false
343
+ }
344
+ const tree = value.tree
345
+ const visual = value.visual
346
+ return (
347
+ captureRecord(tree.viewport) &&
348
+ captureInteger(tree.viewport.width) &&
349
+ tree.viewport.width > 0 &&
350
+ captureInteger(tree.viewport.height) &&
351
+ tree.viewport.height > 0 &&
352
+ captureInteger(tree.rootNodeId) &&
353
+ Array.isArray(tree.nodes) &&
354
+ tree.nodes.every(captureNode) &&
355
+ (visual === undefined ||
356
+ (visual.kind === 'png' &&
357
+ (visual.producer === 'headless-cpu-app-surface' ||
358
+ visual.producer === 'browser-compositor-app-surface') &&
359
+ typeof visual.dataUri === 'string' &&
360
+ visual.dataUri.startsWith('data:image/png;base64,') &&
361
+ captureInteger(visual.pixelWidth) &&
362
+ visual.pixelWidth > 0 &&
363
+ captureInteger(visual.pixelHeight) &&
364
+ visual.pixelHeight > 0))
365
+ )
366
+ }
367
+
368
+ export function isRnxCaptureRegionAtlas(value: unknown): value is RnxCaptureRegionAtlas {
369
+ if (
370
+ !captureRecord(value) ||
371
+ value.kind !== 'png-atlas' ||
372
+ value.producer !== 'headless-cpu-app-surface' ||
373
+ typeof value.dataUri !== 'string' ||
374
+ !value.dataUri.startsWith('data:image/png;base64,') ||
375
+ !captureNumber(value.width) ||
376
+ value.width <= 0 ||
377
+ !captureNumber(value.height) ||
378
+ value.height <= 0 ||
379
+ !captureInteger(value.pixelWidth) ||
380
+ value.pixelWidth <= 0 ||
381
+ !captureInteger(value.pixelHeight) ||
382
+ value.pixelHeight <= 0 ||
383
+ !Array.isArray(value.regions) ||
384
+ value.regions.length === 0 ||
385
+ value.regions.length > 256
386
+ ) {
387
+ return false
388
+ }
389
+ const scale = value.pixelWidth / value.width
390
+ if (
391
+ !Number.isInteger(scale) ||
392
+ scale < 1 ||
393
+ scale > 4 ||
394
+ Math.abs(value.height * scale - value.pixelHeight) > 0.000_001
395
+ ) {
396
+ return false
397
+ }
398
+ const keys = new Set<string>()
399
+ for (const region of value.regions) {
400
+ if (
401
+ !captureRecord(region) ||
402
+ typeof region.key !== 'string' ||
403
+ region.key.length === 0 ||
404
+ keys.has(region.key) ||
405
+ !captureInteger(region.nodeId) ||
406
+ region.nodeId <= 0 ||
407
+ !captureRect(region.frame) ||
408
+ region.frame.width <= 0 ||
409
+ region.frame.height <= 0 ||
410
+ !captureRect(region.source) ||
411
+ region.source.x < 0 ||
412
+ region.source.y < 0 ||
413
+ region.source.width !== region.frame.width ||
414
+ region.source.height !== region.frame.height ||
415
+ region.source.x + region.source.width > value.width ||
416
+ region.source.y + region.source.height > value.height
417
+ ) {
418
+ return false
419
+ }
420
+ keys.add(region.key)
421
+ }
422
+ return true
423
+ }
@@ -0,0 +1,15 @@
1
+ export const RNX_CLOUD_MAX_ARTIFACT_BYTES = 20 * 1024 * 1024
2
+
3
+ export interface RnxCloudClaim {
4
+ id: string
5
+ expiresAt: number
6
+ }
7
+
8
+ export interface RnxCloudCreateReceipt {
9
+ simId: string
10
+ artifact: {
11
+ id: `sha256:${string}`
12
+ bytes: number
13
+ }
14
+ claim: RnxCloudClaim
15
+ }
package/src/home-paths.ts CHANGED
@@ -18,6 +18,7 @@
18
18
  // ├── runtime-upgraded.json one-shot notice written by the daemon's
19
19
  // │ background updater, consumed (printed + deleted)
20
20
  // │ by the next interactive CLI run
21
+ // ├── cli-update.json cached standalone CLI release check + notice state
21
22
  // ├── automatic-cleanup-v1.json completion marker for the one-time safe
22
23
  // │ cleanup introduced for legacy unbounded data
23
24
  // └── config.json user prefs: update channel, cdn origin override,
@@ -33,6 +34,7 @@ export const ACTIVE_RUNTIME_FILE = 'active'
33
34
  export const DAEMON_LOCKFILE = 'daemon.json'
34
35
  export const DEV_BRIDGES_DIR = 'dev-bridges'
35
36
  export const CONFIG_FILE = 'config.json'
37
+ export const CLI_UPDATE_FILE = 'cli-update.json'
36
38
  export const DAEMON_HEARTBEAT_STALE_MS = 30_000
37
39
 
38
40
  export function rnxHomeDir(): string {
@@ -93,8 +95,8 @@ export function isDevWorkstation(): boolean {
93
95
  // the persistent launchd / systemd daemon must never auto-install or run on a
94
96
  // dev checkout or a dev workstation. either makes the background agent point at
95
97
  // moving workspace artifacts / dev env and serve stale assets that shadow the
96
- // live dev stack. setup and the launchd-spawned `sootsim serve` boot consult
97
- // this; a foreground `sootsim serve` the dev starts
98
+ // live dev stack. setup and the launchd-spawned `rnx serve` boot consult
99
+ // this; a foreground `rnx serve` the dev starts
98
100
  // by hand is intentional and stays allowed.
99
101
  export function shouldSkipPersistentDaemon(): boolean {
100
102
  return isSootsimDevCheckout() || isDevWorkstation()
@@ -176,6 +178,10 @@ export function configFilePath(): string {
176
178
  return path.join(rnxHomeDir(), CONFIG_FILE)
177
179
  }
178
180
 
181
+ export function cliUpdateFilePath(): string {
182
+ return path.join(rnxHomeDir(), CLI_UPDATE_FILE)
183
+ }
184
+
179
185
  // --- shared rnx config ---------------------------------------------------
180
186
  //
181
187
  // ~/.rnx/config.json is the single user-level config file every rnx
@@ -291,7 +297,7 @@ export function readOrCreateAnonymousId(): string {
291
297
  // the only place its "updated to vX" line lands is the daemon's own stderr,
292
298
  // which nobody reads. it also drops a one-shot notice file here; the next
293
299
  // interactive CLI invocation prints it once and deletes it, so users learn
294
- // their engine moved without having to run `sootsim version`.
300
+ // their engine moved without having to run `rnx version`.
295
301
 
296
302
  export interface RuntimeUpgradeNotice {
297
303
  from: string | null
@@ -67,7 +67,7 @@ interface PendingPromptEcho {
67
67
  export interface AgentHostOptions {
68
68
  /** callback so hosts with a Contrast-env context (electron, dev middleware)
69
69
  * can inject their own exclude list. defaults to the env-derived list
70
- * the rest of the repo uses, so `sootsim serve` standalone does the
70
+ * the rest of the repo uses, so `rnx serve` standalone does the
71
71
  * right thing. */
72
72
  getExcludePorts?: () => number[]
73
73
  /** returns the active CLI lease for a sim. used only for prompt routing to
@@ -165,7 +165,7 @@ interface BridgeCliLease {
165
165
  }
166
166
 
167
167
  // interactive commands change app state and must respect the lease.
168
- // observational/lifecycle commands (evaluate/tree/query/resolve/screenshot/focus/call/close)
168
+ // observational/lifecycle commands (evaluate/tree/query/resolve/screenshot/capture/focus/call/close)
169
169
  // pass through without taking or checking a lease so cleanup never gets stuck
170
170
  // behind a stale cli owner.
171
171
  // `call` used to acquire a lease too, but most call paths are queries
@@ -228,7 +228,7 @@ export interface BridgeHostOptions {
228
228
  portFallbackCount?: number
229
229
  /** when true, write ~/.rnx/daemon.json on successful bind + update
230
230
  * it on a heartbeat interval + remove it on close. only the standalone
231
- * `sootsim serve` process should set this — tests, vite plugin, and
231
+ * `rnx serve` process should set this — tests, vite plugin, and
232
232
  * anything embedded should leave it off to avoid clobbering a real
233
233
  * daemon's lockfile. defaults to false. */
234
234
  writeLockfile?: boolean
@@ -249,7 +249,7 @@ export interface BridgeHostOptions {
249
249
  * resolves the daemon's own loopback host (localhost:<runtimePort>) to
250
250
  * the dev `contrast.localhost:3000` stack, so a *prod* CLI user's preview
251
251
  * recording would upload to a localhost stack that doesn't exist. the
252
- * `sootsim serve` command resolves this with the same probe the
252
+ * `rnx serve` command resolves this with the same probe the
253
253
  * upload itself uses (`resolveDefaultUploadOrigin`). */
254
254
  contrastOrigin?: string
255
255
  /** override the abandoned-sim GC TTL (ms). defaults to
@@ -325,7 +325,7 @@ const CAMERA_FIXTURE_NAME_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/
325
325
  * the same server on the same port, but a daemon whose preferred 7668
326
326
  * was taken falls back to 7669+. without this the runtime page would
327
327
  * hardcode the 7668 default in `resolveBridgePort` and register on a
328
- * *different* daemon (or none), so `sootsim open` times out waiting for
328
+ * *different* daemon (or none), so `rnx open` times out waiting for
329
329
  * a sim that connected to the wrong bridge.
330
330
  * - `window.__sootsimContrastOrigin` — the contrast origin for auth /
331
331
  * billing / preview uploads. the daemon serves runtimes from its own
@@ -400,8 +400,8 @@ export class SootSimBridgeHost {
400
400
  private static USER_BOOT_LEASE_TTL_MS = 60_000
401
401
  private static SIM_RECONNECT_TTL_MS = 30_000
402
402
  // abandoned-tab GC. the ws heartbeat only reaps sims whose socket went
403
- // dead — but a background tab from a prior `sootsim open` / QA / test run
404
- // keeps its socket alive (the page still pongs) forever, so `sootsim list`
403
+ // dead — but a background tab from a prior `rnx open` / QA / test run
404
+ // keeps its socket alive (the page still pongs) forever, so `rnx list`
405
405
  // accretes dozens of zombie sims that all time out on every command (QA
406
406
  // F21-3, carried F19-2). reap a sim only when it is provably nobody's:
407
407
  // not primary, not user-focused, no CLI attached, no active lease, and no
@@ -409,7 +409,7 @@ export class SootSimBridgeHost {
409
409
  // terminal 4001 code, so the client closes the abandoned window instead
410
410
  // of reconnecting the zombie straight back in.
411
411
  private static SIM_IDLE_REAP_TTL_MS = 30 * 60_000
412
- // automation sims (spawned by the playwright driver: `sootsim open --driver
412
+ // automation sims (spawned by the playwright driver: `rnx open --driver
413
413
  // playwright`, the factory/screenshot capture path — `meta.sootsimHostDriver
414
414
  // === 'playwright'`) are unattended one-shot browsers, never a human's tab. a
415
415
  // capture run opens one per slide/app/retry and the 30-min human/dev TTL lets
@@ -440,7 +440,7 @@ export class SootSimBridgeHost {
440
440
  // dropped, sim crashed) leave their server-side WebSocket sitting "open"
441
441
  // forever. ping every WS_HEARTBEAT_INTERVAL_MS; if the previous round's
442
442
  // ping was never answered, terminate(). that fires 'close' which runs the
443
- // sim-cleanup path and stops `sootsim list` from showing 8 zombie
443
+ // sim-cleanup path and stops `rnx list` from showing 8 zombie
444
444
  // sims that all time out on every command.
445
445
  private wsHeartbeatTimer: NodeJS.Timeout | null = null
446
446
  private wsIsAlive = new WeakMap<WebSocket, boolean>()
@@ -462,7 +462,7 @@ export class SootSimBridgeHost {
462
462
  constructor(opts: BridgeHostOptions = {}) {
463
463
  this.preferredPort = opts.port || DEFAULT_SOOTSIM_BRIDGE_PORT
464
464
  this.port = this.preferredPort
465
- // default off — callers that want the lockfile (sootsim serve) opt in.
465
+ // default off — callers that want the lockfile (rnx serve) opt in.
466
466
  this.shouldWriteLockfile = opts.writeLockfile === true
467
467
  this.shouldWriteDevLockfile = opts.writeDevLockfile === true
468
468
  this.getShellPort = opts.getShellPort ?? null
@@ -1920,7 +1920,7 @@ export class SootSimBridgeHost {
1920
1920
  this.reapIdleSims(now)
1921
1921
  }
1922
1922
 
1923
- // GC abandoned sims so `sootsim list` stays usable across long dev / QA /
1923
+ // GC abandoned sims so `rnx list` stays usable across long dev / QA /
1924
1924
  // test sessions (F21-3). conservative on purpose: a sim is only reaped
1925
1925
  // when every "someone cares about this" signal is absent.
1926
1926
  //
@@ -1936,7 +1936,7 @@ export class SootSimBridgeHost {
1936
1936
  // using is the primary and/or holds a fresh user-active lease; one that is
1937
1937
  // none of those and idle past the TTL is abandoned regardless of a stale
1938
1938
  // focus flag.
1939
- // automation sims are spawned by the playwright driver (`sootsim open
1939
+ // automation sims are spawned by the playwright driver (`rnx open
1940
1940
  // --driver playwright`, the factory/screenshot capture path), which tags them
1941
1941
  // with `meta.sootsimHostDriver === 'playwright'` via the host's addInitScript.
1942
1942
  // they are unattended one-shot browsers, never a human's tab, so they get a
@@ -2551,6 +2551,8 @@ export class SootSimBridgeHost {
2551
2551
  return 'evaluated page state'
2552
2552
  case 'screenshot':
2553
2553
  return 'captured screenshot'
2554
+ case 'capture':
2555
+ return 'captured app screen'
2554
2556
  case 'tap':
2555
2557
  return 'sent tap event'
2556
2558
  case 'keyboard':
@@ -17,9 +17,9 @@
17
17
 
18
18
  import fs from 'node:fs'
19
19
  import path from 'node:path'
20
- import { dispatchPlaneCommand, indexVersion } from '@contrast/harness/plane'
20
+ import { dispatchPlaneCommand, indexVersion } from '@rnx/box/plane'
21
21
  import { CheckoutFilePlane } from '../../cli/commands/box/checkout-plane'
22
- import type { ProjectFilePlane } from '@contrast/harness/plane'
22
+ import type { ProjectFilePlane } from '@rnx/box/plane'
23
23
  import type { WebSocket } from 'ws'
24
24
 
25
25
  const WS_OPEN = 1
package/src/index.ts CHANGED
@@ -35,6 +35,27 @@ export type {
35
35
  SimSemanticResolveSelector,
36
36
  SimSemanticSelector,
37
37
  } from './bridge-contract'
38
+ export {
39
+ isRnxCaptureRegionAtlas,
40
+ isRnxScreenCapture,
41
+ RNX_SCREEN_CAPTURE_VERSION,
42
+ type RnxCaptureAccessibility,
43
+ type RnxCaptureAccessibilityState,
44
+ type RnxCaptureAccessibilityValue,
45
+ type RnxCaptureAtlasRegion,
46
+ type RnxCaptureInteractionState,
47
+ type RnxCaptureNode,
48
+ type RnxCaptureRect,
49
+ type RnxCaptureRegionAtlas,
50
+ type RnxCaptureRegionRequest,
51
+ type RnxCaptureSource,
52
+ type RnxCaptureStyleTokenBinding,
53
+ type RnxCaptureTextLine,
54
+ type RnxCaptureTextSegment,
55
+ type RnxCaptureTree,
56
+ type RnxCaptureVisualEvidence,
57
+ type RnxScreenCapture,
58
+ } from './capture-contract'
38
59
 
39
60
  export { rnxPlugin } from './vite-plugin-one'
40
61
  export { default as metroPlugin } from './metro-plugin'
@@ -42,7 +42,7 @@ export function alignLoopbackBundleUrlWithProbe(
42
42
  // unreliably on small CI runners (tens of seconds when metro is warm,
43
43
  // minutes cold, sometimes past any wait-ready budget). a minified bundle
44
44
  // parses/executes far faster and reboots reliably. gated on
45
- // SOOTSIM_PREVIEW_PROD_BUNDLE so normal interactive `sootsim open` keeps
45
+ // SOOTSIM_PREVIEW_PROD_BUNDLE so normal interactive `rnx open` keeps
46
46
  // dev=true. app-agnostic: rewrites whatever metro/launchAsset advertised.
47
47
  function applyPreviewProdBundle(parsed: URL): void {
48
48
  if (!process.env.SOOTSIM_PREVIEW_PROD_BUNDLE) return
@@ -95,7 +95,7 @@ export function normalizeNativeDevBundleUrl(bundleUrl: string): string {
95
95
  }
96
96
  }
97
97
 
98
- // explicit override used by `sootsim open` to flip metro's `hot` query when
98
+ // explicit override used by `rnx open` to flip metro's `hot` query when
99
99
  // the caller knows their context: interactive opens want `hot=true` so metro
100
100
  // includes its HMR/react-refresh runtime in the bundle prelude and pushes
101
101
  // edits as `update` events; `--driver playwright` / agent-test runs want
@@ -20,7 +20,7 @@ export interface RunScreenshotsOverrides {
20
20
  captureOnly?: boolean
21
21
  composeOnly?: boolean
22
22
  // copy/colors pulled from the team's org deck to overlay onto the plan's
23
- // compose slides (sootsim screenshot appstore --org … --repo …).
23
+ // compose slides (rnx screenshot appstore --org … --repo …).
24
24
  orgDeckFrames?: OrgDeckFrame[] | null
25
25
  }
26
26
 
@@ -1,6 +1,6 @@
1
1
  // fetch the team's screenshot deck (the copy / colors founders set up in the
2
2
  // org dashboard) and overlay it onto a screenshots plan, so the programmatic
3
- // `sootsim screenshot appstore --plan … --org … --repo …` flow renders the org's
3
+ // `rnx screenshot appstore --plan … --org … --repo …` flow renders the org's
4
4
  // words + brand gradient instead of the local yaml's. the plan still owns
5
5
  // which screen each slide captures and the device frame; the deck owns copy +
6
6
  // colors. 3d scenes don't translate to the cli's 2d composer, so only copy and
@@ -1,7 +1,7 @@
1
1
  import { readFile, readdir, stat } from 'node:fs/promises'
2
2
  import { join } from 'node:path'
3
3
  import { parsePlistSource } from './plist.ts'
4
- import type { AppPreferenceValue, AppSettingsSpecifier } from '@sootsim/globals'
4
+ import type { AppPreferenceValue, AppSettingsSpecifier } from '@rnx/globals'
5
5
 
6
6
  const MAX_PLIST_BYTES = 1_048_576
7
7
  const MAX_CHILD_DEPTH = 8