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.
- package/README.md +19 -6
- package/cli/app-fonts.ts +1 -1
- package/cli/bin.ts +118 -26
- package/cli/bridge-diagnostics.ts +1 -1
- package/cli/bridge-flow-runner.ts +9 -9
- package/cli/browser-evals.ts +3 -3
- package/cli/cli-update.ts +261 -0
- package/cli/cloud-client.ts +407 -0
- package/cli/cloud-dispatch.ts +165 -0
- package/cli/cloud-session.ts +156 -0
- package/cli/command-contract.ts +2 -2
- package/cli/command-registry.ts +9 -0
- package/cli/commands/agent-wrapper.ts +4 -4
- package/cli/commands/agent.ts +3 -3
- package/cli/commands/box/checkout-plane.ts +16 -8
- package/cli/commands/box/rnx-command.ts +2 -2
- package/cli/commands/box/search-command.ts +2 -2
- package/cli/commands/box.ts +123 -70
- package/cli/commands/config.ts +3 -2
- package/cli/commands/control.ts +6 -6
- package/cli/commands/cpu-profile.ts +2 -2
- package/cli/commands/daemon.ts +7 -7
- package/cli/commands/debug.ts +2 -2
- package/cli/commands/detox.ts +7 -7
- package/cli/commands/diagnose.ts +1 -1
- package/cli/commands/electron.ts +1 -1
- package/cli/commands/film.ts +1 -1
- package/cli/commands/flow.ts +4 -4
- package/cli/commands/inspect/core.ts +58 -7
- package/cli/commands/inspect/env.ts +5 -3
- package/cli/commands/inspect/shared.ts +2 -1
- package/cli/commands/inspect/wait-event.ts +1 -1
- package/cli/commands/inspect/wait-ready.ts +15 -5
- package/cli/commands/inspect.ts +31 -9
- package/cli/commands/install-desktop.ts +2 -2
- package/cli/commands/maestro.ts +3 -3
- package/cli/commands/permissions.ts +1 -1
- package/cli/commands/platform.ts +91 -0
- package/cli/commands/record.ts +3 -3
- package/cli/commands/report-issue.ts +1 -1
- package/cli/commands/runtime.ts +1 -1
- package/cli/commands/screenshot-mode.ts +1 -1
- package/cli/commands/screenshot.ts +25 -0
- package/cli/commands/serve.ts +1 -1
- package/cli/commands/skills.ts +1 -1
- package/cli/commands/timeline.ts +1 -1
- package/cli/commands/upgrade.ts +8 -34
- package/cli/commands/upload.ts +3 -3
- package/cli/commands/version.ts +14 -3
- package/cli/commands/what-happened.ts +2 -2
- package/cli/current-sim.ts +43 -30
- package/cli/desktop-companion.ts +1 -1
- package/cli/drivers/electron.ts +1 -1
- package/cli/drivers/playwright.ts +3 -3
- package/cli/drivers/types.ts +4 -4
- package/cli/flow-file.ts +3 -3
- package/cli/help-core.ts +18 -11
- package/cli/hidden-runtime-alias.ts +1 -1
- package/cli/hints.ts +2 -2
- package/cli/maestro-js.ts +2 -2
- package/cli/outbound-endpoints.ts +40 -6
- package/cli/parse-args.ts +1 -1
- package/cli/registry.ts +1 -1
- package/cli/resolve-assets.ts +2 -2
- package/cli/secure-file.ts +31 -0
- package/cli/shell-init.ts +98 -0
- package/cli/ws-bridge.ts +18 -8
- package/detox/expectations.ts +1 -1
- package/detox/gestures.ts +3 -3
- package/detox/index.ts +3 -3
- package/dist-lib/agent-daemon-client.cjs +1 -1
- package/dist-lib/agent-events.cjs +1 -1
- package/dist-lib/agent-identity.cjs +1 -1
- package/dist-lib/agent-sessions.cjs +1 -1
- package/dist-lib/attached-projects.cjs +1 -1
- package/dist-lib/auth/shared-session.cjs +1 -1
- package/dist-lib/backend-origin.cjs +1 -1
- package/dist-lib/beta.cjs +1 -1
- package/dist-lib/beta.mjs +1 -1
- package/dist-lib/bridge-constants.cjs +1 -1
- package/dist-lib/bridge-contract-input.cjs +15 -8
- package/dist-lib/bridge-contract-input.mjs +15 -8
- package/dist-lib/bridge-contract.cjs +1 -1
- package/dist-lib/bridge-contract.mjs +1 -1
- package/dist-lib/capture-contract.cjs +107 -0
- package/dist-lib/capture-contract.mjs +80 -0
- package/dist-lib/cli-constants.cjs +1 -1
- package/dist-lib/cloud-contract.cjs +32 -0
- package/dist-lib/cloud-contract.mjs +7 -0
- package/dist-lib/config.cjs +1 -1
- package/dist-lib/detox/index.cjs +4 -4
- package/dist-lib/dev-bundle-resolution.cjs +1 -1
- package/dist-lib/home-paths.cjs +9 -1
- package/dist-lib/host/bridge-host.cjs +15 -10
- package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
- package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
- package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
- package/dist-lib/host/replacement-module-handler.cjs +1 -1
- package/dist-lib/host/websocket-proxy.cjs +1 -1
- package/dist-lib/index.cjs +248 -73
- package/dist-lib/jump-to-source-babel.cjs +1 -1
- package/dist-lib/menu.cjs +1 -1
- package/dist-lib/menu.mjs +1 -1
- package/dist-lib/metro-production-bundle.cjs +1 -1
- package/dist-lib/metro-production-bundle.mjs +1 -1
- package/dist-lib/metro.cjs +1 -1
- package/dist-lib/profiles.cjs +1 -1
- package/dist-lib/public-brand.cjs +1 -1
- package/dist-lib/react-native-host-modules.cjs +1 -1
- package/dist-lib/react-native-host-modules.mjs +1 -1
- package/dist-lib/render-mode.cjs +1 -1
- package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
- package/dist-lib/sdk.cjs +44 -6
- package/dist-lib/sdk.mjs +44 -6
- package/dist-lib/skills.cjs +435 -155
- package/dist-lib/vite.cjs +1 -1
- package/package.json +20 -4
- package/scripts/dev-server-scanner.ts +3 -3
- package/skills/contrast/SKILL.md +1 -1
- package/skills/rnx-perf/SKILL.md +68 -52
- package/src/app-url.ts +2 -3
- package/src/bridge-contract-input.ts +17 -6
- package/src/bridge-contract.ts +25 -4
- package/src/capture-contract.ts +423 -0
- package/src/cloud-contract.ts +15 -0
- package/src/home-paths.ts +9 -3
- package/src/host/agent-host.ts +1 -1
- package/src/host/bridge-host.ts +13 -11
- package/src/host/plane-host.ts +2 -2
- package/src/index.ts +21 -0
- package/src/native-dev-bundle-url.ts +2 -2
- package/src/screenshots/orchestrate.ts +1 -1
- package/src/screenshots/org-deck.ts +1 -1
- package/src/settings-bundle.ts +1 -1
- package/src/sim-client.ts +4 -0
- package/src/vite-plugin-one.ts +2 -2
- package/src/vite-plugin.ts +1 -1
- package/cli/commands/box/cloud-box.ts +0 -294
package/cli/help-core.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { cliCommandMetas } from '@
|
|
1
|
+
import { cliCommandMetas } from '@rnx/skills/cli'
|
|
2
2
|
import {
|
|
3
3
|
renderCliCommandHelp,
|
|
4
4
|
renderCliGroupHelp,
|
|
5
5
|
renderCliIndexHelp,
|
|
6
|
-
} from '@
|
|
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
|
|
41
|
-
return `rnx
|
|
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
|
|
44
|
+
rnx ${command}
|
|
45
45
|
|
|
46
|
-
The browser backend selects the Factory-owned
|
|
47
|
-
|
|
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([
|
|
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
|
|
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 =
|
|
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 {
|
package/cli/hints.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// configurable, identity-scoped hint registry for the
|
|
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 `
|
|
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,
|
|
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
|
-
//
|
|
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/
|
|
119
|
-
{
|
|
120
|
-
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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 '@
|
|
1
|
+
export * from '@rnx/skills/cli'
|
|
2
2
|
export * from './command-registry'
|
package/cli/resolve-assets.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// resolve
|
|
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
|
|
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
|
-
//
|
|
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
|
|
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 {
|
|
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
|
|
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
|
|
469
|
-
if (
|
|
470
|
-
simId =
|
|
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
|
|
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.
|
package/detox/expectations.ts
CHANGED
|
@@ -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 '@
|
|
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 @
|
|
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/
|
|
6
|
-
import type {} from '@
|
|
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 — @
|
|
1431
|
-
// SAME code path `
|
|
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 `
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
261
|
-
const selector = recordValue(value,
|
|
264
|
+
function semanticResolveSelector(value, path) {
|
|
265
|
+
const selector = recordValue(value, path);
|
|
262
266
|
if (selector.testID !== void 0) {
|
|
263
|
-
return { testID: stringValue(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,
|
|
270
|
+
return { text: stringValue(selector.text, `${path}.text`) };
|
|
267
271
|
}
|
|
268
|
-
return validationFailure(
|
|
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
|
-
|
|
297
|
+
selector: semanticResolveSelector(condition.selector, "waitFor.condition.selector")
|
|
291
298
|
},
|
|
292
299
|
...timeoutMs === void 0 ? {} : { timeoutMs }
|
|
293
300
|
};
|