rnxsim 0.1.380 → 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 +43 -26
- 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 +21 -5
- 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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// transport-agnostic
|
|
1
|
+
// transport-agnostic rnx inspect-verb kernels.
|
|
2
2
|
//
|
|
3
3
|
// each function here sends the smallest bridge command its verb needs and
|
|
4
4
|
// returns *structured data* — no arg parsing or stdout. the
|
|
@@ -29,7 +29,7 @@ import type {
|
|
|
29
29
|
SootSimTimelineQuery,
|
|
30
30
|
SootSimTimelineQueryResult,
|
|
31
31
|
SootSimTimelineSummary,
|
|
32
|
-
} from '@
|
|
32
|
+
} from '@rnx/globals'
|
|
33
33
|
|
|
34
34
|
// resolve a `--max-ms <n>` flag, tolerating the spellings agents and humans
|
|
35
35
|
// actually type (`--maxMs`, `--maxms`, `--max_ms`). a silently-ignored
|
|
@@ -48,8 +48,9 @@ export function resolveMaxMsFlag(args: string[], fallbackMs: number): number {
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
// the minimal bridge an inspect kernel needs: a JSON command channel into a
|
|
51
|
-
//
|
|
51
|
+
// rnx instance.
|
|
52
52
|
export interface InspectBridge {
|
|
53
|
+
readonly plane?: 'local' | 'cloud'
|
|
53
54
|
send(
|
|
54
55
|
cmd: { type: string; [k: string]: any },
|
|
55
56
|
opts?: { timeoutMs?: number },
|
|
@@ -285,7 +286,7 @@ export async function inspectDescribe(
|
|
|
285
286
|
// fields with `styling:true` / `--styling`.
|
|
286
287
|
//
|
|
287
288
|
// the SAME LayoutElement shape is produced by both preview surfaces:
|
|
288
|
-
// - native/
|
|
289
|
+
// - native/rnx — `LAYOUT_GET_NATIVE_EVAL` reads the engine node tree via
|
|
289
290
|
// __sootsimTest.listInspectable() (the source `describe` uses), where each
|
|
290
291
|
// SootSimInspectInfo already carries absolute layout and optional style /
|
|
291
292
|
// box-model data.
|
|
@@ -703,7 +704,7 @@ export function layoutGetNativeEval(opts: LayoutExtractionOptions = {}): string
|
|
|
703
704
|
export const LAYOUT_GET_NATIVE_EVAL = layoutGetNativeEval()
|
|
704
705
|
|
|
705
706
|
// `get-layout` (native) — bounding boxes for visible meaningful elements on the
|
|
706
|
-
//
|
|
707
|
+
// rnx/native preview. transport-agnostic: the CLI runs it over its WS
|
|
707
708
|
// bridge, the agent runs it over its in-process preview bridge.
|
|
708
709
|
export async function inspectGetLayout(
|
|
709
710
|
bridge: InspectBridge,
|
|
@@ -1128,7 +1129,10 @@ export async function inspectWaitSelector(
|
|
|
1128
1129
|
{
|
|
1129
1130
|
type: 'waitFor',
|
|
1130
1131
|
waitForOptions: {
|
|
1131
|
-
condition: {
|
|
1132
|
+
condition: {
|
|
1133
|
+
type: gone ? 'absent' : 'present',
|
|
1134
|
+
selector: { testID: testId },
|
|
1135
|
+
},
|
|
1132
1136
|
timeoutMs,
|
|
1133
1137
|
},
|
|
1134
1138
|
},
|
|
@@ -1352,6 +1356,51 @@ export interface LogFilterOptions {
|
|
|
1352
1356
|
// not installed yet, return an empty snapshot so validation tools don't turn a
|
|
1353
1357
|
// missing diagnostics buffer into the app failure being diagnosed.
|
|
1354
1358
|
export async function inspectLogs(bridge: InspectBridge): Promise<LogEntry[]> {
|
|
1359
|
+
if (bridge.plane === 'cloud') {
|
|
1360
|
+
const state: unknown = await bridge.send({
|
|
1361
|
+
type: 'state',
|
|
1362
|
+
stateOptions: {
|
|
1363
|
+
screenshot: false,
|
|
1364
|
+
tree: false,
|
|
1365
|
+
route: false,
|
|
1366
|
+
errors: false,
|
|
1367
|
+
logs: true,
|
|
1368
|
+
logLimit: 100,
|
|
1369
|
+
},
|
|
1370
|
+
})
|
|
1371
|
+
const recentLogs =
|
|
1372
|
+
state &&
|
|
1373
|
+
typeof state === 'object' &&
|
|
1374
|
+
Array.isArray(Reflect.get(state, 'recentLogs'))
|
|
1375
|
+
? Reflect.get(state, 'recentLogs')
|
|
1376
|
+
: []
|
|
1377
|
+
return recentLogs.flatMap((entry: unknown, index: number) => {
|
|
1378
|
+
if (!entry || typeof entry !== 'object') return []
|
|
1379
|
+
const text = Reflect.get(entry, 'text')
|
|
1380
|
+
const rawLevel = Reflect.get(entry, 'level')
|
|
1381
|
+
if (typeof text !== 'string') return []
|
|
1382
|
+
const level: LogLevel =
|
|
1383
|
+
rawLevel === 'log' ||
|
|
1384
|
+
rawLevel === 'info' ||
|
|
1385
|
+
rawLevel === 'warn' ||
|
|
1386
|
+
rawLevel === 'error' ||
|
|
1387
|
+
rawLevel === 'debug'
|
|
1388
|
+
? rawLevel
|
|
1389
|
+
: 'error'
|
|
1390
|
+
const at = Reflect.get(entry, 'at')
|
|
1391
|
+
const ts = typeof at === 'number' && Number.isFinite(at) ? at : 0
|
|
1392
|
+
return [
|
|
1393
|
+
{
|
|
1394
|
+
id: `cloud-${ts}-${index}`,
|
|
1395
|
+
source: 'rnx-cloud',
|
|
1396
|
+
level,
|
|
1397
|
+
ts,
|
|
1398
|
+
args: [text],
|
|
1399
|
+
stack: null,
|
|
1400
|
+
},
|
|
1401
|
+
]
|
|
1402
|
+
})
|
|
1403
|
+
}
|
|
1355
1404
|
const res = (await bridge.send({
|
|
1356
1405
|
type: 'evaluate',
|
|
1357
1406
|
code: `(() => {
|
|
@@ -1402,7 +1451,9 @@ export function filterLogEntries(
|
|
|
1402
1451
|
if (!opts.showInternal) {
|
|
1403
1452
|
out = out.filter((e) => {
|
|
1404
1453
|
const first = e.args[0]
|
|
1405
|
-
return !(
|
|
1454
|
+
return !(
|
|
1455
|
+
typeof first === 'string' && /^\[(?:rnx|sootsim)(?:\s[^\]]*)?\]/.test(first)
|
|
1456
|
+
)
|
|
1406
1457
|
})
|
|
1407
1458
|
}
|
|
1408
1459
|
if (opts.level) out = out.filter((e) => opts.level!.has(e.level))
|
|
@@ -13,10 +13,12 @@ const AGENT_ENV_VARS = [
|
|
|
13
13
|
'AIDER_MODEL',
|
|
14
14
|
]
|
|
15
15
|
|
|
16
|
-
export function isAgentEnv(
|
|
17
|
-
|
|
16
|
+
export function isAgentEnv(
|
|
17
|
+
env: Readonly<Record<string, string | undefined>> = process.env,
|
|
18
|
+
): boolean {
|
|
19
|
+
if (env.RNX_AGENT === '0') return false
|
|
18
20
|
for (const name of AGENT_ENV_VARS) {
|
|
19
|
-
const v =
|
|
21
|
+
const v = env[name]
|
|
20
22
|
if (v && v.trim() && v !== '0') return true
|
|
21
23
|
}
|
|
22
24
|
return false
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
type WsBridge,
|
|
13
13
|
} from '../../ws-bridge'
|
|
14
14
|
import { getShellState, inspectWaitReady, isShellCommandUnavailable } from './core'
|
|
15
|
-
import type { SootSimScreenTransitionWaitResult } from '@
|
|
15
|
+
import type { SootSimScreenTransitionWaitResult } from '@rnx/globals'
|
|
16
16
|
|
|
17
17
|
// shell-state helpers live in the transport-agnostic kernel module so the CLI
|
|
18
18
|
// and the in-browser agent share one implementation. re-exported here so
|
|
@@ -93,6 +93,7 @@ export async function maybeWaitForStartedScreenTransitions(
|
|
|
93
93
|
bridge: WsBridge,
|
|
94
94
|
opts: { verbose?: boolean } = {},
|
|
95
95
|
) {
|
|
96
|
+
if (bridge.plane === 'cloud') return
|
|
96
97
|
try {
|
|
97
98
|
const result = await callTestBridge<SootSimScreenTransitionWaitResult>(
|
|
98
99
|
bridge,
|
|
@@ -33,13 +33,23 @@ export async function runWaitReadySubcommand(opts: {
|
|
|
33
33
|
errors,
|
|
34
34
|
bridgeError,
|
|
35
35
|
} = await inspectWaitReady(bridge, timeoutMs, {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
36
|
+
...(bridge.plane !== 'cloud'
|
|
37
|
+
? {
|
|
38
|
+
onProgress(status) {
|
|
39
|
+
console.error(
|
|
40
|
+
` still waiting after ${status.elapsedMs}ms — ${waitReadyReason(status)} (nodes: ${status.nodes}, targets: ${status.targets}, live surfaces: ${status.liveFrameActive ? status.liveFrameChannels : 0}, errors: ${status.errors})`,
|
|
41
|
+
)
|
|
42
|
+
},
|
|
43
|
+
}
|
|
44
|
+
: {}),
|
|
41
45
|
})
|
|
42
46
|
if (ready) {
|
|
47
|
+
if (bridge.plane === 'cloud') {
|
|
48
|
+
console.log(
|
|
49
|
+
` ready in ${elapsedMs}ms: ${nodes} nodes, ${targets} targets${liveFrameActive ? `, ${liveFrameChannels} live surfaces` : ''}`,
|
|
50
|
+
)
|
|
51
|
+
return
|
|
52
|
+
}
|
|
43
53
|
const remainingMs = timeoutMs - elapsedMs
|
|
44
54
|
const idleBudgetMs = Math.max(100, Math.min(10_000, remainingMs))
|
|
45
55
|
const idle = await waitForSootsimIdle({
|
package/cli/commands/inspect.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
//
|
|
2
|
-
// connects to the
|
|
1
|
+
// rnx inspect — dump node tree, accessibility info, query nodes
|
|
2
|
+
// connects to the rnx WS bridge (no Playwright)
|
|
3
3
|
|
|
4
|
-
import { getCliInspectVerbMeta } from '@
|
|
5
|
-
import { renderCliCommandHelp, renderCliGroupHelp } from '@
|
|
4
|
+
import { getCliInspectVerbMeta } from '@rnx/skills/cli'
|
|
5
|
+
import { renderCliCommandHelp, renderCliGroupHelp } from '@rnx/skills/cli/renderers'
|
|
6
6
|
import { DEFAULT_SOOTSIM_BRIDGE_PORT } from '../../src/bridge-constants'
|
|
7
7
|
import { DEFAULT_SOOTSIM_SHELL_URL } from '../../src/cli-constants'
|
|
8
8
|
import { resetGuestAppStateEval } from '../app-state-reset'
|
|
@@ -105,7 +105,7 @@ import type {
|
|
|
105
105
|
SootSimRequestEntry,
|
|
106
106
|
SootSimScreenTransitionWaitResult,
|
|
107
107
|
SootSimScrollPerformanceTrace,
|
|
108
|
-
} from '@
|
|
108
|
+
} from '@rnx/globals'
|
|
109
109
|
|
|
110
110
|
interface InspectOptions {
|
|
111
111
|
port?: number
|
|
@@ -1112,7 +1112,7 @@ export async function runInspect(args: string[], opts: InspectOptions) {
|
|
|
1112
1112
|
` usage: ${inspectCommand(name)}${tail ? ` ${tail}` : ''}`
|
|
1113
1113
|
|
|
1114
1114
|
if (!subcommand || args.includes('--help') || args.includes('-h')) {
|
|
1115
|
-
// all help output comes from the registry in packages/
|
|
1115
|
+
// all help output comes from the registry in packages/rnx-skills
|
|
1116
1116
|
// so `rnx do --help`, `rnx get --help`, the website docs, and
|
|
1117
1117
|
// the Contrast skill markdown stay in lock-step. a drift check in
|
|
1118
1118
|
// test/sootsimCliRegistry.test.ts fails CI if a verb lands without a
|
|
@@ -1183,7 +1183,7 @@ export async function runInspect(args: string[], opts: InspectOptions) {
|
|
|
1183
1183
|
}
|
|
1184
1184
|
|
|
1185
1185
|
// `rnx list --drivers` — pure local registry view, no bridge needed.
|
|
1186
|
-
// short-circuit here so users can discover drivers even when no
|
|
1186
|
+
// short-circuit here so users can discover drivers even when no rnx
|
|
1187
1187
|
// session is running.
|
|
1188
1188
|
if (
|
|
1189
1189
|
subcommand === 'list' &&
|
|
@@ -1840,6 +1840,28 @@ export async function runInspect(args: string[], opts: InspectOptions) {
|
|
|
1840
1840
|
console.error(' resolves testID, id, then text — dumps full node info as JSON')
|
|
1841
1841
|
rnxExit(1)
|
|
1842
1842
|
}
|
|
1843
|
+
if (bridge.plane === 'cloud') {
|
|
1844
|
+
const byTestId = await inspectFind(bridge, { testId: matcher })
|
|
1845
|
+
const byText = byTestId?.result
|
|
1846
|
+
? null
|
|
1847
|
+
: await inspectFind(bridge, { text: matcher })
|
|
1848
|
+
const node = byTestId?.result ?? byText?.result ?? null
|
|
1849
|
+
console.log(
|
|
1850
|
+
JSON.stringify(
|
|
1851
|
+
node
|
|
1852
|
+
? {
|
|
1853
|
+
matcher,
|
|
1854
|
+
found: true,
|
|
1855
|
+
resolvedVia: byTestId?.result ? 'testID' : 'text',
|
|
1856
|
+
node,
|
|
1857
|
+
}
|
|
1858
|
+
: { matcher, found: false },
|
|
1859
|
+
null,
|
|
1860
|
+
2,
|
|
1861
|
+
),
|
|
1862
|
+
)
|
|
1863
|
+
break
|
|
1864
|
+
}
|
|
1843
1865
|
// resolve in order: testID → id → text. first hit wins, includes the
|
|
1844
1866
|
// resolved transform and a parent chain so agents can skip the usual
|
|
1845
1867
|
// __sootsimRoot walk.
|
|
@@ -3607,7 +3629,7 @@ export async function runInspect(args: string[], opts: InspectOptions) {
|
|
|
3607
3629
|
if (!code) {
|
|
3608
3630
|
console.error(inspectUsage('js', '<javascript>'))
|
|
3609
3631
|
console.error('')
|
|
3610
|
-
console.error(' runs the snippet in the engine realm.
|
|
3632
|
+
console.error(' runs the snippet in the engine realm. rnx is the')
|
|
3611
3633
|
console.error(' canonical state surface — reach into it directly.')
|
|
3612
3634
|
console.error('')
|
|
3613
3635
|
console.error(' examples:')
|
|
@@ -3679,7 +3701,7 @@ export async function runInspect(args: string[], opts: InspectOptions) {
|
|
|
3679
3701
|
}
|
|
3680
3702
|
|
|
3681
3703
|
case 'globals': {
|
|
3682
|
-
// show all available
|
|
3704
|
+
// show all available rnx globals and their methods
|
|
3683
3705
|
const info = await bridge.send({
|
|
3684
3706
|
type: 'evaluate',
|
|
3685
3707
|
code: `(async () => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
//
|
|
1
|
+
// rnx desktop install — download and install the optional desktop GUI
|
|
2
2
|
//
|
|
3
|
-
// the CLI + daemon are the canonical
|
|
3
|
+
// the CLI + daemon are the canonical rnx surface; this command is only
|
|
4
4
|
// for users who want the Electron-based GUI companion on top of that.
|
|
5
5
|
//
|
|
6
6
|
// fetches the right platform artifact from https://contrast.dev/api/electron-release/
|
package/cli/commands/maestro.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
//
|
|
3
3
|
// users with an existing `.maestro/` directory can swap `maestro test`
|
|
4
4
|
// for `rnx maestro test` and have their flows execute against
|
|
5
|
-
//
|
|
5
|
+
// rnx's in-browser engine. the heavy lifting lives in `flow.ts` +
|
|
6
6
|
// `bridge-flow-runner.ts`; this file just re-maps the maestro argv shape
|
|
7
7
|
// onto the existing flow runner and handles directory iteration + init.
|
|
8
8
|
|
|
@@ -102,7 +102,7 @@ partial:
|
|
|
102
102
|
clearKeychain — warn-only, rnx has no keychain surface.
|
|
103
103
|
when.platform — rnx reports iOS for this runner.
|
|
104
104
|
openLink: app routing works; no OS-level Safari fallback.
|
|
105
|
-
launchApp.resetRuntime —
|
|
105
|
+
launchApp.resetRuntime — rnx-only extension for recorded benchmarks:
|
|
106
106
|
keep tenant storage/auth, but soft-reload the guest app inside launchApp.
|
|
107
107
|
takeScreenshot — maestro string form works unchanged; rnx also accepts
|
|
108
108
|
{ path, withFrame } for framed export.
|
|
@@ -402,7 +402,7 @@ export async function runMaestro(
|
|
|
402
402
|
// one. sending it on as `--url` made it an app target instead, which left
|
|
403
403
|
// the playback bridge on whichever world resolveBridgeWorld picks by
|
|
404
404
|
// default (a live Vite dev shell selected from the development registry).
|
|
405
|
-
// so `
|
|
405
|
+
// so `rnx --port <installed daemon> maestro …` silently ran the flow
|
|
406
406
|
// against the dev engine, then failed trying to resolve a bundle at the
|
|
407
407
|
// bridge port. an explicit command-level `--port` still wins, since
|
|
408
408
|
// parseBridgeCliArgs takes the last occurrence.
|
package/cli/commands/platform.ts
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
} from 'sootsim-engine/settings'
|
|
16
16
|
import { settingsStore } from 'sootsim-engine/settings/store'
|
|
17
17
|
import { rnxPublicBrand } from '../../src/public-brand'
|
|
18
|
+
import { rethrowIfExit } from '../run-rnx'
|
|
18
19
|
|
|
19
20
|
const PLATFORM_LABEL: Record<RuntimePlatform, string> = {
|
|
20
21
|
ios: 'iOS',
|
|
@@ -25,6 +26,36 @@ function isDeviceModel(value: string): value is DeviceModel {
|
|
|
25
26
|
return value in devices
|
|
26
27
|
}
|
|
27
28
|
|
|
29
|
+
function cloudBundleFromArgs(args: string[]): string | null {
|
|
30
|
+
const positional: string[] = []
|
|
31
|
+
for (let index = 0; index < args.length; index++) {
|
|
32
|
+
const arg = args[index]
|
|
33
|
+
if (arg === '--cloud') continue
|
|
34
|
+
if (arg === '--device') {
|
|
35
|
+
index++
|
|
36
|
+
continue
|
|
37
|
+
}
|
|
38
|
+
if (
|
|
39
|
+
arg === '--sim' ||
|
|
40
|
+
arg === '--session' ||
|
|
41
|
+
arg === '--tab' ||
|
|
42
|
+
arg.startsWith('--sim=') ||
|
|
43
|
+
arg.startsWith('--session=') ||
|
|
44
|
+
arg.startsWith('--tab=')
|
|
45
|
+
) {
|
|
46
|
+
throw new Error('rnx ios --cloud creates a new simulator and cannot target --sim')
|
|
47
|
+
}
|
|
48
|
+
if (arg.startsWith('-')) {
|
|
49
|
+
throw new Error(`rnx ios --cloud does not support ${arg}`)
|
|
50
|
+
}
|
|
51
|
+
positional.push(arg)
|
|
52
|
+
}
|
|
53
|
+
if (positional.length !== 1) {
|
|
54
|
+
throw new Error('usage: rnx ios --cloud <local bundle.js> [--device <model>]')
|
|
55
|
+
}
|
|
56
|
+
return positional[0] ?? null
|
|
57
|
+
}
|
|
58
|
+
|
|
28
59
|
export async function runPlatformCommand(
|
|
29
60
|
platform: RuntimePlatform,
|
|
30
61
|
args: string[],
|
|
@@ -34,6 +65,7 @@ export async function runPlatformCommand(
|
|
|
34
65
|
// contradiction shows up here as a platform mismatch. refuse it by name
|
|
35
66
|
// rather than silently overriding whichever one the user meant.
|
|
36
67
|
const explicitDevice = args.find((_, i) => args[i - 1] === '--device')
|
|
68
|
+
let selectedDevice: DeviceModel
|
|
37
69
|
if (explicitDevice !== undefined) {
|
|
38
70
|
if (!isDeviceModel(explicitDevice)) {
|
|
39
71
|
console.error(
|
|
@@ -53,6 +85,7 @@ export async function runPlatformCommand(
|
|
|
53
85
|
)
|
|
54
86
|
return 1
|
|
55
87
|
}
|
|
88
|
+
selectedDevice = explicitDevice
|
|
56
89
|
} else {
|
|
57
90
|
// keep the user's own device when it already belongs to the platform they
|
|
58
91
|
// asked for, so `rnx ios` after `rnx device set iphone-16` does not
|
|
@@ -60,6 +93,64 @@ export async function runPlatformCommand(
|
|
|
60
93
|
const current = settingsStore.get('deviceModel')
|
|
61
94
|
if (getRuntimePlatform(current) !== platform) {
|
|
62
95
|
settingsStore.apply({ deviceModel: DEFAULT_DEVICE_BY_PLATFORM[platform] })
|
|
96
|
+
selectedDevice = DEFAULT_DEVICE_BY_PLATFORM[platform]
|
|
97
|
+
} else {
|
|
98
|
+
selectedDevice = current
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (args.includes('--cloud')) {
|
|
103
|
+
if (platform !== 'ios') {
|
|
104
|
+
console.error(
|
|
105
|
+
` ${rnxPublicBrand.commandName} android --cloud is not available; RNX Cloud currently supports iOS`,
|
|
106
|
+
)
|
|
107
|
+
return 1
|
|
108
|
+
}
|
|
109
|
+
try {
|
|
110
|
+
const bundlePath = cloudBundleFromArgs(args)
|
|
111
|
+
if (!bundlePath) throw new Error('rnx ios --cloud requires a local bundle.js')
|
|
112
|
+
const {
|
|
113
|
+
publishCloudSessionToShell,
|
|
114
|
+
readCloudSession,
|
|
115
|
+
requireCloudSessionShellUpdate,
|
|
116
|
+
} = await import('../cloud-session')
|
|
117
|
+
if (readCloudSession()) {
|
|
118
|
+
throw new Error(
|
|
119
|
+
'this shell already has an RNX Cloud simulator; run `rnx close` or create the next simulator in another shell',
|
|
120
|
+
)
|
|
121
|
+
}
|
|
122
|
+
const descriptor = requireCloudSessionShellUpdate()
|
|
123
|
+
const { authHeaderOrExit } = await import('../auth')
|
|
124
|
+
const { closeCloudSimulator, createCloudSimulator } =
|
|
125
|
+
await import('../cloud-client')
|
|
126
|
+
const { header } = authHeaderOrExit('ios --cloud')
|
|
127
|
+
const created = await createCloudSimulator({
|
|
128
|
+
bundlePath,
|
|
129
|
+
device: selectedDevice,
|
|
130
|
+
authorization: header,
|
|
131
|
+
})
|
|
132
|
+
try {
|
|
133
|
+
publishCloudSessionToShell(created.session, descriptor)
|
|
134
|
+
} catch (error) {
|
|
135
|
+
await closeCloudSimulator(created.session)
|
|
136
|
+
throw error
|
|
137
|
+
}
|
|
138
|
+
const receipt = created.receipt
|
|
139
|
+
console.log(` cloud sim: ${receipt.simId}`)
|
|
140
|
+
console.log(` artifact: ${receipt.artifact.id} (${receipt.artifact.bytes} bytes)`)
|
|
141
|
+
console.log(` claim expires: ${receipt.claim.expiresAt}`)
|
|
142
|
+
console.log(
|
|
143
|
+
' reads: describe, find, get tree|node|count, wait ready|selector, logs, state, screenshot',
|
|
144
|
+
)
|
|
145
|
+
return 0
|
|
146
|
+
} catch (error) {
|
|
147
|
+
rethrowIfExit(error)
|
|
148
|
+
console.error(
|
|
149
|
+
` ${rnxPublicBrand.commandName} ios --cloud failed: ${
|
|
150
|
+
error instanceof Error ? error.message : String(error)
|
|
151
|
+
}`,
|
|
152
|
+
)
|
|
153
|
+
return 1
|
|
63
154
|
}
|
|
64
155
|
}
|
|
65
156
|
|
package/cli/commands/record.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// rnx record — drives the engine's built-in canvas recorder over
|
|
2
2
|
// the WS bridge. no playwright, no ffmpeg. encoding happens in the
|
|
3
|
-
// running
|
|
3
|
+
// running rnx page (webm via MediaRecorder, mp4 via WebCodecs, gif
|
|
4
4
|
// via gifenc, raw png frames via a shared bitmap sampler).
|
|
5
5
|
|
|
6
6
|
import {
|
|
@@ -180,7 +180,7 @@ function readRepoScope(args: string[]): RepoScope {
|
|
|
180
180
|
|
|
181
181
|
export async function runRecord(args: string[], opts: RecordOptions) {
|
|
182
182
|
// note: `--help` / `-h` never reach here — bin.ts intercepts them and
|
|
183
|
-
// renders the registry-driven help page (packages/
|
|
183
|
+
// renders the registry-driven help page (packages/rnx-skills meta.ts).
|
|
184
184
|
|
|
185
185
|
const sub = findRecordSubcommand(args)
|
|
186
186
|
if (sub) {
|
|
@@ -583,7 +583,7 @@ async function assertRecorderAvailable(bridge: WsBridge) {
|
|
|
583
583
|
|
|
584
584
|
// frame-capturing recordings (video / gif / png / combined) drive their
|
|
585
585
|
// encoder off requestAnimationFrame and throttled timers, which browsers
|
|
586
|
-
// clamp to ~1fps when the
|
|
586
|
+
// clamp to ~1fps when the rnx tab is hidden (backgrounded, or its chrome
|
|
587
587
|
// window minimized). that silently produced a frozen-looking file and is
|
|
588
588
|
// exactly why agents started pre-checking `document.hidden` by hand. refuse
|
|
589
589
|
// up front with an actionable message instead. checkSimHealth already emits
|
package/cli/commands/runtime.ts
CHANGED
|
@@ -136,7 +136,7 @@ async function activateVersion(version: string) {
|
|
|
136
136
|
const notified = await tellDaemonRuntimeChanged(version)
|
|
137
137
|
if (!notified) {
|
|
138
138
|
console.log(
|
|
139
|
-
` (no daemon running — next
|
|
139
|
+
` (no daemon running — next rnx/electron launch will pick up ${version})`,
|
|
140
140
|
)
|
|
141
141
|
}
|
|
142
142
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// rnx mode screenshot — toggle the "screenshot mode" chrome overlay
|
|
2
|
-
// in the running
|
|
2
|
+
// in the running rnx sim. orthogonal to `--no-shell` on the
|
|
3
3
|
// `screenshot` command: this hides the DOM browser chrome (rail, menubar,
|
|
4
4
|
// window controls) while still rendering the iOS shell inside the canvas.
|
|
5
5
|
//
|
|
@@ -13,6 +13,7 @@ import { launchReapedChrome } from '../../../../scripts/lib/reap-browser'
|
|
|
13
13
|
import { DEFAULT_SOOTSIM_BRIDGE_PORT } from '../../src/bridge-constants'
|
|
14
14
|
import { composeFramedScreenshot } from '../../src/screenshots/frame-compose'
|
|
15
15
|
import { buildResolveRectEval, type SampleRect } from '../browser-evals'
|
|
16
|
+
import { createCloudBridgeForParsed } from '../cloud-client'
|
|
16
17
|
import { rnxExit } from '../run-rnx'
|
|
17
18
|
import {
|
|
18
19
|
callInBridge,
|
|
@@ -120,6 +121,30 @@ examples:
|
|
|
120
121
|
stripValueFlags: ['--output', '--area', '--id', '--text'],
|
|
121
122
|
})
|
|
122
123
|
const outputArg = resolveOutputPath(args)
|
|
124
|
+
const cloudBridge = createCloudBridgeForParsed(parsed)
|
|
125
|
+
if (cloudBridge) {
|
|
126
|
+
try {
|
|
127
|
+
const dataUrl: unknown = await cloudBridge.send({ type: 'screenshot' })
|
|
128
|
+
const pngPrefix = 'data:image/png;base64,'
|
|
129
|
+
if (typeof dataUrl !== 'string' || !dataUrl.startsWith(pngPrefix)) {
|
|
130
|
+
throw new Error('RNX Cloud screenshot returned invalid image data')
|
|
131
|
+
}
|
|
132
|
+
const outputPath = resolve(process.cwd(), outputArg || '/tmp/rnx-inspect.png')
|
|
133
|
+
mkdirSync(dirname(outputPath), { recursive: true })
|
|
134
|
+
const png = Buffer.from(dataUrl.slice(pngPrefix.length), 'base64')
|
|
135
|
+
if (png.length === 0) throw new Error('RNX Cloud screenshot returned no image data')
|
|
136
|
+
writeFileSync(outputPath, png)
|
|
137
|
+
console.log(` saved: ${outputPath}`)
|
|
138
|
+
} catch (error) {
|
|
139
|
+
console.error(
|
|
140
|
+
` screenshot failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
141
|
+
)
|
|
142
|
+
rnxExit(1)
|
|
143
|
+
} finally {
|
|
144
|
+
cloudBridge.close()
|
|
145
|
+
}
|
|
146
|
+
return
|
|
147
|
+
}
|
|
123
148
|
const rect = await parseRect(args)
|
|
124
149
|
const usingBridge = await probeBridge(parsed.wsPort)
|
|
125
150
|
|
package/cli/commands/serve.ts
CHANGED
|
@@ -56,7 +56,7 @@ examples:
|
|
|
56
56
|
}
|
|
57
57
|
const quiet = args.includes('--quiet') || args.includes('-q')
|
|
58
58
|
|
|
59
|
-
// only one bridge per
|
|
59
|
+
// only one bridge per rnx home. refuse to start a second one
|
|
60
60
|
// regardless of requested port, since both would try to write the same
|
|
61
61
|
// daemon.json and electron/cli clients only read one lockfile.
|
|
62
62
|
const existingLock = readDaemonLockfile()
|
package/cli/commands/skills.ts
CHANGED
|
@@ -216,7 +216,7 @@ function symlinkPointsTo(dest: string, src: string): boolean {
|
|
|
216
216
|
}
|
|
217
217
|
|
|
218
218
|
// a store entry left by an older CLI is safe to upgrade: either a real dir
|
|
219
|
-
// whose only content is a SKILL.md symlink into
|
|
219
|
+
// whose only content is a SKILL.md symlink into an rnx skills source (the
|
|
220
220
|
// pre-dir-layout install — possibly broken after the source moved), or a dir
|
|
221
221
|
// holding a byte-identical copy of the skill. only our installer creates
|
|
222
222
|
// those shapes, so replacing them can't lose user work.
|
package/cli/commands/timeline.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
//
|
|
3
3
|
// `rnx record` is for video/screen capture; `rnx debug record` is
|
|
4
4
|
// for the engine-internal debug-channel ring; this command is the third
|
|
5
|
-
// surface and explicitly distinct: it manages SootSim.bridges.timeline,
|
|
5
|
+
// surface and explicitly distinct: it manages the SootSim.bridges.timeline API,
|
|
6
6
|
// the unified, agent-facing semantic event stream across host / shell
|
|
7
7
|
// worker / tenant worker.
|
|
8
8
|
//
|
package/cli/commands/upgrade.ts
CHANGED
|
@@ -10,12 +10,15 @@ import { compareSemver } from '@contrast/runtime-delivery'
|
|
|
10
10
|
import { getCliVersion } from '../../src/cli-version'
|
|
11
11
|
import { isSootsimDevCheckout, readActiveRuntime, runtimeDir } from '../../src/home-paths'
|
|
12
12
|
import { resolveRNXAppConfig } from '../app-config'
|
|
13
|
+
import {
|
|
14
|
+
DEFAULT_CLI_RELEASE_ORIGIN,
|
|
15
|
+
fetchCliReleaseFile,
|
|
16
|
+
refreshCliUpdateCache,
|
|
17
|
+
} from '../cli-update'
|
|
13
18
|
import { fetchRuntimeReleaseNotes, formatRuntimeReleaseNotes } from '../runtime-notes'
|
|
14
19
|
import { IS_STANDALONE } from '../standalone'
|
|
15
20
|
import { runRuntime } from './runtime'
|
|
16
21
|
|
|
17
|
-
const DEFAULT_CLI_RELEASE_ORIGIN = 'https://r2.rnxsim.com/rnx-cli'
|
|
18
|
-
const CLI_VERSION_PATTERN = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/
|
|
19
22
|
const SHA256_PATTERN = /^[a-f0-9]{64}$/
|
|
20
23
|
|
|
21
24
|
export interface UpgradeOptions {
|
|
@@ -100,14 +103,7 @@ export async function upgradeCli(options: UpgradeOptions = {}): Promise<void> {
|
|
|
100
103
|
'',
|
|
101
104
|
)
|
|
102
105
|
const current = getCliVersion()
|
|
103
|
-
const latest =
|
|
104
|
-
.toString('utf8')
|
|
105
|
-
.trim()
|
|
106
|
-
if (!CLI_VERSION_PATTERN.test(latest)) {
|
|
107
|
-
throw new Error(
|
|
108
|
-
`rnx update service returned an invalid version: ${latest || '(empty)'}`,
|
|
109
|
-
)
|
|
110
|
-
}
|
|
106
|
+
const { latestVersion: latest } = await refreshCliUpdateCache({ releaseOrigin })
|
|
111
107
|
if (compareSemver(latest, current) <= 0) {
|
|
112
108
|
console.log(` v${current} (latest)`)
|
|
113
109
|
return
|
|
@@ -115,7 +111,7 @@ export async function upgradeCli(options: UpgradeOptions = {}): Promise<void> {
|
|
|
115
111
|
|
|
116
112
|
const artifact = cliArtifactName()
|
|
117
113
|
const artifactUrl = `${releaseOrigin}/${latest}/${artifact}`
|
|
118
|
-
const expectedSha = (await
|
|
114
|
+
const expectedSha = (await fetchCliReleaseFile(`${artifactUrl}.sha256`, 8_000))
|
|
119
115
|
.toString('utf8')
|
|
120
116
|
.trim()
|
|
121
117
|
if (!SHA256_PATTERN.test(expectedSha)) {
|
|
@@ -124,7 +120,7 @@ export async function upgradeCli(options: UpgradeOptions = {}): Promise<void> {
|
|
|
124
120
|
|
|
125
121
|
console.log(` v${current} → v${latest}`)
|
|
126
122
|
console.log(` downloading ${artifact}...`)
|
|
127
|
-
const executable = await
|
|
123
|
+
const executable = await fetchCliReleaseFile(artifactUrl, 120_000)
|
|
128
124
|
const actualSha = createHash('sha256').update(executable).digest('hex')
|
|
129
125
|
if (actualSha !== expectedSha) {
|
|
130
126
|
throw new Error(
|
|
@@ -157,28 +153,6 @@ function cliArtifactName(): string {
|
|
|
157
153
|
return `rnx-${platform}-${process.arch}`
|
|
158
154
|
}
|
|
159
155
|
|
|
160
|
-
async function fetchRequired(url: string, timeoutMs: number): Promise<Buffer> {
|
|
161
|
-
let response: Response
|
|
162
|
-
try {
|
|
163
|
-
response = await fetch(url, {
|
|
164
|
-
headers: { accept: 'application/octet-stream' },
|
|
165
|
-
signal: AbortSignal.timeout(timeoutMs),
|
|
166
|
-
})
|
|
167
|
-
} catch (error) {
|
|
168
|
-
throw new Error(
|
|
169
|
-
`could not reach the rnx update service: ${
|
|
170
|
-
error instanceof Error ? error.message : String(error)
|
|
171
|
-
}`,
|
|
172
|
-
)
|
|
173
|
-
}
|
|
174
|
-
if (!response.ok) {
|
|
175
|
-
throw new Error(
|
|
176
|
-
`rnx update download failed: ${response.status} ${response.statusText}`,
|
|
177
|
-
)
|
|
178
|
-
}
|
|
179
|
-
return Buffer.from(await response.arrayBuffer())
|
|
180
|
-
}
|
|
181
|
-
|
|
182
156
|
function verifyCandidate(candidatePath: string, expectedVersion: string): void {
|
|
183
157
|
const result = spawnSync(candidatePath, ['--version'], {
|
|
184
158
|
encoding: 'utf8',
|