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
package/cli/help-core.ts CHANGED
@@ -1,9 +1,9 @@
1
- import { cliCommandMetas } from '@sootsim/skills/cli'
1
+ import { cliCommandMetas } from '@rnx/skills/cli'
2
2
  import {
3
3
  renderCliCommandHelp,
4
4
  renderCliGroupHelp,
5
5
  renderCliIndexHelp,
6
- } from '@sootsim/skills/cli/renderers'
6
+ } from '@rnx/skills/cli/renderers'
7
7
  import { formatFlagHelp } from 'sootsim-engine/settings/cli-flags'
8
8
  import {
9
9
  DEFAULT_SOOTSIM_BRIDGE_PORT,
@@ -37,14 +37,14 @@ function backendLabel(command: string, args: readonly string[]): string {
37
37
  return `available backends: ${available.join(', ')}`
38
38
  }
39
39
 
40
- function renderWebHelp(): string {
41
- return `rnx web — select or ensure the current branch's existing web preview
40
+ function renderPreviewHelp(command: 'web' | 'ios' | 'android'): string {
41
+ return `rnx ${command} — select or ensure the current branch's existing ${command} preview
42
42
 
43
43
  usage:
44
- rnx web
44
+ rnx ${command}
45
45
 
46
- The browser backend selects the Factory-owned web iframe. It never launches a
47
- browser, host process, or Cloudflare runtime.`
46
+ The browser backend selects the Factory-owned ${command} iframe. It never launches a
47
+ host process or Cloudflare runtime.`
48
48
  }
49
49
 
50
50
  function renderFactoryBrowserLeaves(command: string): string | null {
@@ -70,7 +70,12 @@ function renderFactoryBrowserLeaves(command: string): string | null {
70
70
  }
71
71
 
72
72
  function renderRootAvailability(backend: RnxBackendKind): string {
73
- const visibleCommands = new Set([...cliCommandMetas.map((meta) => meta.id), 'web'])
73
+ const visibleCommands = new Set([
74
+ ...cliCommandMetas.map((meta) => meta.id),
75
+ 'web',
76
+ 'ios',
77
+ 'android',
78
+ ])
74
79
  const visibleRegistry = RNX_COMMAND_REGISTRY.filter((entry) =>
75
80
  visibleCommands.has(entry.name),
76
81
  )
@@ -94,15 +99,17 @@ export function renderRnxHelp(options: RnxHelpOptions): string {
94
99
  if (!command) {
95
100
  return `${renderCliIndexHelp(HELP_CONTEXT, formatFlagHelp()).trimEnd()}
96
101
 
97
- browser preview selector:
102
+ browser preview selectors:
98
103
  rnx web select the existing Factory web preview
104
+ rnx ios select the existing Factory iOS preview
105
+ rnx android select the existing Factory Android preview
99
106
 
100
107
  ${renderRootAvailability(options.backend)}`
101
108
  }
102
109
 
103
110
  let body: string | null
104
- if (command === 'web') {
105
- body = renderWebHelp()
111
+ if (command === 'web' || command === 'ios' || command === 'android') {
112
+ body = renderPreviewHelp(command)
106
113
  } else if (options.group === command && !options.prefer) {
107
114
  body = renderCliGroupHelp(command, HELP_CONTEXT, formatFlagHelp())
108
115
  } else {
@@ -1,4 +1,4 @@
1
- import { getCliInspectVerbMetaByPath } from '@sootsim/skills/cli'
1
+ import { getCliInspectVerbMetaByPath } from '@rnx/skills/cli'
2
2
  import type { ParsedArgs } from './parse-args'
3
3
 
4
4
  const HIDDEN_RUNTIME_ALIAS_GROUPS = ['do', 'get'] as const
package/cli/hints.ts CHANGED
@@ -1,4 +1,4 @@
1
- // configurable, identity-scoped hint registry for the sootsim CLI.
1
+ // configurable, identity-scoped hint registry for the rnx CLI.
2
2
  //
3
3
  // two problems this solves:
4
4
  // 1. the CLI scatters "hint: ..." console.logs across many command files,
@@ -205,7 +205,7 @@ defineHint<[string]>('subtree-root-not-found', {
205
205
  })
206
206
 
207
207
  defineHint<[]>('clean-uninstall', {
208
- // shown once on the first `sootsim --help` run so users learn the clean
208
+ // shown once on the first `rnx --help` run so users learn the clean
209
209
  // teardown path. package-manager uninstall hooks are best-effort, so the
210
210
  // hint documents the explicit daemon cleanup command.
211
211
  frequency: 'once-ever',
package/cli/maestro-js.ts CHANGED
@@ -14,9 +14,9 @@
14
14
  // time (maestro-orchestra-models util/Env.kt): regex (?<!\\)\$\{([^$]*)},
15
15
  // result stringified, `\${...}` escapes to a literal, blank exprs become ''.
16
16
  //
17
- // divergences (deliberate, sootsim-side ergonomics — keep this list short):
17
+ // divergences (deliberate, rnx-side ergonomics — keep this list short):
18
18
  // 1. envBinding is seeded from the full process.env, not just MAESTRO_*.
19
- // sootsim flows historically resolve `${SOOTSIM_*}` credentials from the
19
+ // rnx flows historically resolve `${SOOTSIM_*}` credentials from the
20
20
  // shell environment and `rnx maestro --env K=V` writes process.env.
21
21
  // 2. a *bare identifier* template (`${NAME}`) that evaluates to undefined
22
22
  // throws a loud "missing variable" error instead of interpolating the
@@ -102,6 +102,14 @@ export const OUTBOUND_ENDPOINTS: Record<string, OutboundEndpointDeclaration> = {
102
102
  payload:
103
103
  'every file git reports for the current checkout, then shell commands and file reads and writes for as long as the box shell is open',
104
104
  },
105
+ rnx_cloud_sim: {
106
+ category: 'explicit-user-action',
107
+ destination:
108
+ 'https://rnxsim.com by default, or the test or disposable origin named by RNX_CLOUD_ORIGIN',
109
+ preConsent: false,
110
+ payload:
111
+ 'the Metro JavaScript bundle selected by rnx ios --cloud, then structured simulator commands and lifecycle requests',
112
+ },
105
113
  }
106
114
 
107
115
  export interface OutboundCallDeclaration {
@@ -113,22 +121,47 @@ export interface OutboundCallDeclaration {
113
121
  // a new fetch, websocket, node HTTP request, or TCP connection has no entry and
114
122
  // fails the package test until its destination and consent behavior are declared.
115
123
  export const DECLARED_OUTBOUND_CALLS: Record<string, OutboundCallDeclaration> = {
124
+ 'packages/sootsim/cli/cloud-client.ts :: fetch :: `${apiOrigin}/v1/sims`': {
125
+ category: 'rnx_cloud_sim',
126
+ count: 1,
127
+ },
128
+ 'packages/sootsim/cli/cloud-client.ts :: fetch :: `${session.apiOrigin}/v1/sims/${encodeURIComponent(session.simId)}/claim`':
129
+ { category: 'rnx_cloud_sim', count: 1 },
130
+ 'packages/sootsim/cli/cloud-client.ts :: fetch :: `${session.apiOrigin}/v1/sims/${encodeURIComponent(session.simId)}`':
131
+ { category: 'rnx_cloud_sim', count: 1 },
132
+ 'packages/sootsim/cli/cloud-client.ts :: fetch :: `${this.session.apiOrigin}/v1/sims/${encodeURIComponent(this.session.simId)}/commands`':
133
+ { category: 'rnx_cloud_sim', count: 1 },
116
134
  // `rnx box create` uploads the checkout to the box service and then talks to
117
135
  // it. the destination is whatever the user named; there is no default.
118
- 'packages/sootsim/cli/commands/box/cloud-box.ts :: fetch :: endpointOf(base, name, verb)':
119
- { category: 'rnx_cloud_box', count: 1 },
120
- 'packages/sootsim/cli/commands/box/cloud-box.ts :: websocket :: url': {
136
+ 'packages/rnx-cloud-box/src/client.ts :: fetch :: endpointOf(base, name, verb, search)':
137
+ {
138
+ category: 'rnx_cloud_box',
139
+ count: 1,
140
+ },
141
+ 'packages/rnx-cloud-box/src/client.ts :: websocket :: url': {
121
142
  category: 'rnx_cloud_box',
122
143
  count: 1,
123
144
  },
124
145
  // the account's own session meter: starting, listing and heartbeating the
125
146
  // session a cloud box is billed against. no project content.
126
- "packages/sootsim/cli/commands/box/cloud-box.ts :: fetch :: `${options.apiOrigin.replace(/\\/+$/, '')}/v1/rnx/sessions`":
147
+ "packages/rnx-cloud-box/src/client.ts :: fetch :: `${options.apiOrigin.replace(/\\/+$/, '')}/v1/rnx/sessions`":
127
148
  { category: 'contrast_user_action', count: 1 },
128
- 'packages/sootsim/cli/commands/box/cloud-box.ts :: fetch :: url': {
149
+ 'packages/rnx-cloud-box/src/client.ts :: fetch :: url': {
150
+ category: 'contrast_user_action',
151
+ count: 1,
152
+ },
153
+ 'packages/rnx-cloud-box/src/box-do.ts :: fetch :: `${origin}/v1/rnx/sessions`': {
129
154
  category: 'contrast_user_action',
130
155
  count: 1,
131
156
  },
157
+ 'packages/rnx-cloud-box/src/box-do.ts :: fetch :: `${origin}/v1/rnx/sessions/${encodeURIComponent(sessionId)}`':
158
+ { category: 'contrast_user_action', count: 1 },
159
+ // the Cloudflare Worker forwards the already-authenticated request to the
160
+ // selected Durable Object in the same service.
161
+ 'packages/rnx-cloud-box/src/worker.ts :: fetch :: request': {
162
+ category: 'same_origin_runtime',
163
+ count: 1,
164
+ },
132
165
  // a LOCAL box authorizing its own checkout with the loopback bridge daemon.
133
166
  // this one refuses to guess a port, so it cannot reach anything but a bridge
134
167
  // the user's own session is running; see the note on ws-bridge.ts below
@@ -340,7 +373,7 @@ export const DECLARED_OUTBOUND_CALLS: Record<string, OutboundCallDeclaration> =
340
373
  { category: 'contrast_user_action', count: 1 },
341
374
  'packages/sootsim/cli/commands/screenshots-capture.ts :: fetch :: `${apiOrigin}/api/sootsim/screenshot-decks?org=${encodeURIComponent(org)}&repo=${encodeURIComponent(repo)}`':
342
375
  { category: 'contrast_user_action', count: 1 },
343
- 'packages/sootsim/cli/commands/upgrade.ts :: fetch :: url': {
376
+ 'packages/sootsim/cli/cli-update.ts :: fetch :: url': {
344
377
  category: 'cli_delivery',
345
378
  count: 1,
346
379
  },
@@ -589,6 +622,7 @@ export function detectOutboundCalls(repositoryRoot: string): DetectedOutboundCal
589
622
  path.join(repositoryRoot, 'packages/sootsim-engine/src'),
590
623
  path.join(repositoryRoot, 'packages/sootsim-engine/src-electron'),
591
624
  path.join(repositoryRoot, 'packages/contrast-runtime-delivery/src'),
625
+ path.join(repositoryRoot, 'packages/rnx-cloud-box/src'),
592
626
  ]
593
627
  const counts = new Map<string, number>()
594
628
  for (const file of roots.flatMap(sourceFilesUnder)) {
package/cli/parse-args.ts CHANGED
@@ -60,7 +60,7 @@ export const HYPHENATED_GROUPING_VERBS = new Set(['do', 'get', 'wait', 'shell',
60
60
 
61
61
  // known commands — if first positional arg matches, it's a command.
62
62
  // every entry here must have a `cliCommandMetas` entry in
63
- // `packages/sootsim-skills/src/cli/meta.ts` (or sit on the internal
63
+ // `packages/rnx-skills/src/cli/meta.ts` (or sit on the internal
64
64
  // allowlist in `test/sootsimCliRegistry.test.ts`), so help, website docs,
65
65
  // and agent skills can never drift from what the parser accepts.
66
66
  export const COMMANDS = new Set(RNX_NODE_COMMAND_NAMES)
package/cli/registry.ts CHANGED
@@ -1,2 +1,2 @@
1
- export * from '@sootsim/skills/cli'
1
+ export * from '@rnx/skills/cli'
2
2
  export * from './command-registry'
@@ -1,4 +1,4 @@
1
- // resolve sootsim's bundled assets (fonts, icons, sounds)
1
+ // resolve rnx's bundled assets (fonts, icons, sounds)
2
2
  // regardless of how the package was installed (source checkout, npm install, bunx).
3
3
  //
4
4
  // uses import.meta.url to derive the package root, then checks both
@@ -13,7 +13,7 @@ import { rnxPublicBrand } from '../src/public-brand'
13
13
  // when running from source, this file lives at cli/resolve-assets.ts
14
14
  const thisDir = dirname(fileURLToPath(import.meta.url))
15
15
 
16
- // walk up to find package root: look for package.json with name "sootsim"
16
+ // walk up to find package root: look for package.json with the rnxsim package name
17
17
  function findPackageRoot(): string {
18
18
  let dir = thisDir
19
19
  for (let i = 0; i < 5; i++) {
@@ -0,0 +1,31 @@
1
+ import { randomUUID } from 'node:crypto'
2
+ import {
3
+ closeSync,
4
+ fchmodSync,
5
+ fsyncSync,
6
+ mkdirSync,
7
+ openSync,
8
+ renameSync,
9
+ rmSync,
10
+ writeFileSync,
11
+ } from 'node:fs'
12
+ import { dirname } from 'node:path'
13
+
14
+ export function writeSecureJsonAtomic(filepath: string, value: unknown): void {
15
+ mkdirSync(dirname(filepath), { recursive: true })
16
+ const temporary = `${filepath}.tmp-${process.pid}-${randomUUID()}`
17
+ let descriptor: number | null = null
18
+ try {
19
+ descriptor = openSync(temporary, 'wx', 0o600)
20
+ fchmodSync(descriptor, 0o600)
21
+ writeFileSync(descriptor, `${JSON.stringify(value, null, 2)}\n`)
22
+ fsyncSync(descriptor)
23
+ closeSync(descriptor)
24
+ descriptor = null
25
+ renameSync(temporary, filepath)
26
+ } catch (error) {
27
+ if (descriptor !== null) closeSync(descriptor)
28
+ rmSync(temporary, { force: true })
29
+ throw error
30
+ }
31
+ }
@@ -0,0 +1,98 @@
1
+ import {
2
+ RNX_CLOUD_SESSION_ENV,
3
+ RNX_SHELL_UPDATE_SET_PREFIX,
4
+ RNX_SHELL_UPDATE_UNSET,
5
+ } from './cloud-session'
6
+
7
+ export function renderRnxShellInit(shell: string): string {
8
+ if (shell !== 'bash' && shell !== 'zsh') {
9
+ throw new Error('RNX shell integration supports bash and zsh')
10
+ }
11
+ return `rnx() {
12
+ local -a _rnx_shell_args
13
+ local _rnx_shell_command _rnx_shell_create _rnx_shell_update
14
+ local _rnx_shell_session _rnx_shell_status _rnx_shell_traced
15
+ _rnx_shell_args=("$@")
16
+ _rnx_shell_command=''
17
+ _rnx_shell_create=0
18
+ while [ "$#" -gt 0 ]; do
19
+ case "$1" in
20
+ -h|--help|-V|--version|-v|--verbose|--headless) shift ;;
21
+ -p|--port|-d|--device|-t|--theme|--driver|--sim|--session|--tab)
22
+ if [ "$#" -ge 2 ]; then shift 2; else shift; fi
23
+ ;;
24
+ --) break ;;
25
+ -*) shift ;;
26
+ *) _rnx_shell_command="$1"; shift; break ;;
27
+ esac
28
+ done
29
+ case "$_rnx_shell_command" in
30
+ claim|close) ;;
31
+ ios)
32
+ for _rnx_shell_session in "$@"; do
33
+ if [ "$_rnx_shell_session" = '--cloud' ]; then
34
+ _rnx_shell_create=1
35
+ break
36
+ fi
37
+ done
38
+ if [ "$_rnx_shell_create" -ne 1 ]; then
39
+ command rnx "\${_rnx_shell_args[@]}"
40
+ return $?
41
+ fi
42
+ ;;
43
+ *)
44
+ command rnx "\${_rnx_shell_args[@]}"
45
+ return $?
46
+ ;;
47
+ esac
48
+
49
+ if { [ -n "\${BASH_VERSION:-}" ] && [ "\${BASH_SUBSHELL:-0}" -ne 0 ]; } ||
50
+ { [ -n "\${ZSH_VERSION:-}" ] && [ "\${ZSH_SUBSHELL:-0}" -ne 0 ]; }; then
51
+ printf '%s\\n' 'rnx cloud lifecycle commands must run directly in the current shell, not in a pipeline or command substitution' >&2
52
+ return 1
53
+ fi
54
+ if ( : >&9 ) 2>/dev/null; then
55
+ printf '%s\\n' 'rnx cloud shell integration requires file descriptor 9 to be unused' >&2
56
+ return 1
57
+ fi
58
+ if ! ( export ${RNX_CLOUD_SESSION_ENV}=__rnx_shell_probe && unset ${RNX_CLOUD_SESSION_ENV} ) 2>/dev/null; then
59
+ printf '%s\\n' 'rnx cloud shell integration cannot update RNX_CLOUD_SESSION in this shell' >&2
60
+ return 1
61
+ fi
62
+
63
+ case "$-" in
64
+ *x*) _rnx_shell_traced=1; set +x ;;
65
+ *) _rnx_shell_traced=0 ;;
66
+ esac
67
+ exec 9>&1
68
+ _rnx_shell_update="$(RNX_SHELL_UPDATE_FD=3 command rnx "\${_rnx_shell_args[@]}" 3>&1 1>&9)"
69
+ _rnx_shell_status=$?
70
+ exec 9>&-
71
+
72
+ if [ "$_rnx_shell_status" -eq 0 ]; then
73
+ case "$_rnx_shell_update" in
74
+ ${RNX_SHELL_UPDATE_SET_PREFIX}*)
75
+ _rnx_shell_session="\${_rnx_shell_update#${RNX_SHELL_UPDATE_SET_PREFIX}}"
76
+ case "$_rnx_shell_session" in
77
+ ''|*$'\\n'*) _rnx_shell_status=1 ;;
78
+ *) export ${RNX_CLOUD_SESSION_ENV}="$_rnx_shell_session" || _rnx_shell_status=$? ;;
79
+ esac
80
+ ;;
81
+ ${RNX_SHELL_UPDATE_UNSET})
82
+ unset ${RNX_CLOUD_SESSION_ENV} || _rnx_shell_status=$?
83
+ ;;
84
+ *) _rnx_shell_status=1 ;;
85
+ esac
86
+ if [ "$_rnx_shell_status" -ne 0 ]; then
87
+ printf '%s\\n' 'rnx refused an invalid shell session update' >&2
88
+ elif [ "$_rnx_shell_create" -eq 1 ]; then
89
+ command rnx __cloud-confirm
90
+ _rnx_shell_status=$?
91
+ fi
92
+ fi
93
+
94
+ if [ "$_rnx_shell_traced" -eq 1 ]; then set -x; fi
95
+ return "$_rnx_shell_status"
96
+ }
97
+ `
98
+ }
package/cli/ws-bridge.ts CHANGED
@@ -1,10 +1,10 @@
1
- // sootsim CLI ↔ sim WS bridge
1
+ // rnx CLI ↔ sim WS bridge
2
2
  //
3
3
  // the rnx runtime opens a WebSocket server (default port 7668) that
4
4
  // accepts JSON commands like `{ type: 'evaluate', code: 'window.__sootsimTest.getNodeCount()' }`
5
5
  // or `{ type: 'call', path: 'SootSim.bridges.mainShell.launchApp', args: ['photos'] }`
6
6
  // and responds with `{ id, result }` or `{ id, error }`. CLI commands use
7
- // this to drive a running sootsim instance from the terminal.
7
+ // this to drive a running rnx instance from the terminal.
8
8
 
9
9
  import fs from 'node:fs'
10
10
  import path from 'node:path'
@@ -22,7 +22,13 @@ import {
22
22
  rnxHomeDir,
23
23
  type DevBridgeLockfile,
24
24
  } from '../src/home-paths'
25
- import { clearCurrentSimId, getCliIdentity, readCurrentSimId } from './current-sim'
25
+ import { createCloudBridgeForParsed } from './cloud-client'
26
+ import {
27
+ clearCurrentSimId,
28
+ getCliIdentity,
29
+ readCurrentSim,
30
+ readCurrentSimId,
31
+ } from './current-sim'
26
32
 
27
33
  // --- bridge world resolution ---------------------------------------------
28
34
  //
@@ -279,6 +285,7 @@ export function printBridgeWorldNotice(
279
285
  }
280
286
 
281
287
  export interface WsBridge {
288
+ readonly plane?: 'local' | 'cloud'
282
289
  // `opts.timeoutMs` overrides the bridge-wide commandTimeoutMs for a single
283
290
  // call — used for fast readiness probes so a dead/unresponsive sim fails in
284
291
  // seconds instead of stalling the full command timeout.
@@ -423,7 +430,7 @@ export function parseBridgeCliArgs(
423
430
  continue
424
431
  }
425
432
  // `--sim` is the canonical target flag; `--session` / `--tab` are
426
- // documented aliases (CLAUDE.md, the SootSim agent skills, and engine docs all
433
+ // documented aliases (CLAUDE.md, the rnx agent skills, and engine docs all
427
434
  // say `--session <id>`). without these aliases an agent following the docs
428
435
  // would have `--session ef` silently ignored and get the *currently
429
436
  // pinned* sim's tree instead — wrong-app data with no error. accept the
@@ -465,9 +472,9 @@ export function parseBridgeCliArgs(
465
472
  }
466
473
 
467
474
  if (!simId) {
468
- const savedSimId = readCurrentSimId()
469
- if (savedSimId) {
470
- simId = savedSimId.trim() || undefined
475
+ const savedSim = readCurrentSim()
476
+ if (savedSim) {
477
+ simId = savedSim.simId.trim() || undefined
471
478
  simIdSource = 'saved'
472
479
  }
473
480
  }
@@ -601,6 +608,7 @@ export function createBridge(wsPort: number, opts: WsBridgeOptions = {}): WsBrid
601
608
  })
602
609
 
603
610
  return {
611
+ plane: 'local',
604
612
  async send(cmd, callOpts) {
605
613
  // NOTE: `opts` (the createBridge closure arg) carries simId; do not
606
614
  // shadow it with the per-call arg.
@@ -711,6 +719,8 @@ export function createBridge(wsPort: number, opts: WsBridgeOptions = {}): WsBrid
711
719
  }
712
720
 
713
721
  export function createBridgeFromParsed(parsed: ParsedBridgeCliArgs): WsBridge {
722
+ const cloudBridge = createCloudBridgeForParsed(parsed)
723
+ if (cloudBridge) return cloudBridge
714
724
  return createBridge(parsed.wsPort, {
715
725
  commandTimeoutMs: parsed.commandTimeoutMs,
716
726
  simId: parsed.simId,
@@ -792,7 +802,7 @@ export async function checkSimHealth(
792
802
  }
793
803
 
794
804
  /**
795
- * Convenience: run arbitrary JavaScript in the sootsim page and return the
805
+ * Convenience: run arbitrary JavaScript in the rnx page and return the
796
806
  * result. Hides the `type: 'evaluate'` message shape. Read-only by default —
797
807
  * pass `{ acquireLock: true }` when the eval mutates user-facing state so
798
808
  * the server takes/refreshes the cli lease and blocks concurrent agents.
@@ -5,7 +5,7 @@
5
5
  import { dragScrollNode } from './gestures'
6
6
  import type { SootSimElement } from './element-types'
7
7
  import type { Matcher } from './matchers'
8
- import type { SootSimNodeInfo } from '@sootsim/globals'
8
+ import type { SootSimNodeInfo } from '@rnx/globals'
9
9
  import type { Page } from 'playwright'
10
10
 
11
11
  type FindFn = (matcher: Matcher) => Promise<any>
package/detox/gestures.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  import { pollUntil } from '../src/poll-until'
2
- // pull in @sootsim/globals so window.__sootsimTest and
2
+ // pull in @rnx/globals so window.__sootsimTest and
3
3
  // window.__sootsimFindInteractiveCanvas are typed inside page.evaluate
4
4
  // bodies. all sootsim-namespaced globals are declared canonically there —
5
- // add new entries to packages/sootsim-globals/src/index.ts, not here.
6
- import type {} from '@sootsim/globals'
5
+ // add new entries to packages/rnx-globals/src/index.ts, not here.
6
+ import type {} from '@rnx/globals'
7
7
  import type { Page } from 'playwright'
8
8
 
9
9
  // shape of the surface-metrics bridge response we care about. the bridge
package/detox/index.ts CHANGED
@@ -1427,8 +1427,8 @@ export const device = {
1427
1427
  },
1428
1428
 
1429
1429
  // record the SootSim canvas via the engine's headless recorder
1430
- // (window.__sootsimRecorder — @sootsim/plugin-recording). this is the
1431
- // SAME code path `sootsim record video` drives, NOT the rail-button
1430
+ // (window.__sootsimRecorder — @rnx/plugin-recording). this is the
1431
+ // SAME code path `rnx record video` drives, NOT the rail-button
1432
1432
  // path (SootSim.bridges.startRecording wires the dialog state machine
1433
1433
  // and never stashes the resulting blob into __sootsimRecorder.lastBlob,
1434
1434
  // which is what getBlobBase64 streams from).
@@ -1471,7 +1471,7 @@ export const device = {
1471
1471
  },
1472
1472
 
1473
1473
  // stop the headless recorder, stream the resulting blob in chunks (same
1474
- // protocol as `sootsim record`), and write the webm to outPath.
1474
+ // protocol as `rnx record`), and write the webm to outPath.
1475
1475
  async stopAndSaveBridgeRecording(outPath: string): Promise<string> {
1476
1476
  const page = getPage()
1477
1477
  const stopResult = await page.evaluate(async () => {
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.381 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.382 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.381 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.382 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.381 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.382 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.381 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.382 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.381 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.382 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.381 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.382 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.381 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.382 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
package/dist-lib/beta.cjs CHANGED
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.381 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.382 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
package/dist-lib/beta.mjs CHANGED
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.381 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.382 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/beta.ts
4
4
  var IS_BETA = true;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.381 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.382 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.381 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.382 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
@@ -197,8 +197,10 @@ function parseStateOptions(value) {
197
197
  const input = recordValue(value, "state");
198
198
  const route = optionalBoolean(input, "route", "state");
199
199
  const errors = optionalBoolean(input, "errors", "state");
200
+ const logs = optionalBoolean(input, "logs", "state");
200
201
  const depth = optionalNumber(input, "depth", "state");
201
202
  const errorLimit = optionalNumber(input, "errorLimit", "state");
203
+ const logLimit = optionalNumber(input, "logLimit", "state");
202
204
  const layers = input.layers === void 0 ? void 0 : stringValue(input.layers, "state.layers");
203
205
  if (layers !== void 0 && layers !== "full" && layers !== "tenant" && layers !== "shell") {
204
206
  return validationFailure("state.layers", "must be full, tenant, or shell");
@@ -208,9 +210,11 @@ function parseStateOptions(value) {
208
210
  tree: optionalBoolean(input, "tree", "state") ?? true,
209
211
  ...route === void 0 ? {} : { route },
210
212
  ...errors === void 0 ? {} : { errors },
213
+ ...logs === void 0 ? {} : { logs },
211
214
  ...depth === void 0 ? {} : { depth },
212
215
  ...layers === void 0 ? {} : { layers },
213
- ...errorLimit === void 0 ? {} : { errorLimit }
216
+ ...errorLimit === void 0 ? {} : { errorLimit },
217
+ ...logLimit === void 0 ? {} : { logLimit }
214
218
  };
215
219
  }
216
220
  function parseSemanticSelector(value, path) {
@@ -257,15 +261,18 @@ function parseSemanticQuery(value) {
257
261
  }
258
262
  return validationFailure("query.kind", "must be tree, find, or count");
259
263
  }
260
- function parseSemanticResolveSelector(value) {
261
- const selector = recordValue(value, "selector");
264
+ function semanticResolveSelector(value, path) {
265
+ const selector = recordValue(value, path);
262
266
  if (selector.testID !== void 0) {
263
- return { testID: stringValue(selector.testID, "selector.testID") };
267
+ return { testID: stringValue(selector.testID, `${path}.testID`) };
264
268
  }
265
269
  if (selector.text !== void 0) {
266
- return { text: stringValue(selector.text, "selector.text") };
270
+ return { text: stringValue(selector.text, `${path}.text`) };
267
271
  }
268
- return validationFailure("selector", "must select testID or text");
272
+ return validationFailure(path, "must select testID or text");
273
+ }
274
+ function parseSemanticResolveSelector(value) {
275
+ return semanticResolveSelector(value, "selector");
269
276
  }
270
277
  function parseSemanticWaitOptions(value) {
271
278
  const input = recordValue(value, "waitFor");
@@ -287,7 +294,7 @@ function parseSemanticWaitOptions(value) {
287
294
  return {
288
295
  condition: {
289
296
  type,
290
- testID: stringValue(condition.testID, "waitFor.condition.testID")
297
+ selector: semanticResolveSelector(condition.selector, "waitFor.condition.selector")
291
298
  },
292
299
  ...timeoutMs === void 0 ? {} : { timeoutMs }
293
300
  };