rnxsim 0.1.407 → 0.1.409
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 +11 -10
- package/cli/bin.ts +16 -643
- package/cli/commands/assert.ts +6 -6
- package/cli/commands/box/rnx-command.ts +3 -11
- package/cli/commands/control.ts +3 -9
- package/cli/commands/daemon-mac-app.ts +5 -11
- package/cli/commands/daemon.ts +63 -113
- package/cli/drivers/playwright-provisioning.ts +33 -29
- package/cli/drivers/playwright-sim-host.ts +501 -0
- package/cli/drivers/playwright.ts +35 -522
- package/cli/hints.ts +2 -4
- package/cli/internal-child.ts +176 -0
- package/cli/maestro-js.ts +28 -39
- package/cli/main.ts +645 -0
- package/cli/outbound-endpoints.ts +8 -0
- package/cli/self-invocation.ts +34 -0
- package/dist-lib/agent-daemon-client.cjs +35 -25
- package/dist-lib/agent-events.cjs +1 -1
- package/dist-lib/agent-identity.cjs +1 -1
- package/dist-lib/agent-sessions.cjs +35 -25
- 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 +1 -1
- package/dist-lib/bridge-contract-input.mjs +1 -1
- package/dist-lib/bridge-contract.cjs +1 -1
- package/dist-lib/bridge-contract.mjs +1 -1
- package/dist-lib/capture-contract.cjs +2 -2
- package/dist-lib/capture-contract.mjs +2 -2
- package/dist-lib/cli-constants.cjs +1 -1
- package/dist-lib/cloud-contract.cjs +1 -4
- package/dist-lib/cloud-contract.mjs +1 -3
- package/dist-lib/config.cjs +1 -1
- package/dist-lib/detox/index.cjs +1 -1
- package/dist-lib/dev-bundle-resolution.cjs +1 -1
- package/dist-lib/home-paths.cjs +1 -1
- package/dist-lib/host/bridge-host.cjs +25 -15
- 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 +2 -2
- 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-fingerprint-registry.cjs +1 -1
- package/dist-lib/metro-fingerprint-registry.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 +1 -1
- package/dist-lib/sdk.mjs +1 -1
- package/dist-lib/skills.cjs +134 -608
- package/dist-lib/vite.cjs +1 -1
- package/package.json +1 -1
- package/src/agent-daemon-client.ts +2 -2
- package/src/agent-sessions.ts +29 -24
- package/src/capture-contract.ts +3 -0
- package/src/cloud-contract.ts +0 -1
- package/src/vite-plugin.ts +62 -0
package/cli/bin.ts
CHANGED
|
@@ -5,11 +5,12 @@
|
|
|
5
5
|
// website docs, and agent skills all read from there. do not duplicate
|
|
6
6
|
// command listings in this file; run `rnx --help` for the canonical
|
|
7
7
|
// surface.
|
|
8
|
+
//
|
|
9
|
+
// this file only routes. the hidden entry points below each take over the
|
|
10
|
+
// whole process, so the CLI itself lives in `./main` and is imported only once
|
|
11
|
+
// none of them has claimed the run.
|
|
8
12
|
|
|
9
|
-
import {
|
|
10
|
-
import { parseArgs, TOP_LEVEL_RUNTIME_COMMANDS } from './parse-args'
|
|
11
|
-
import { RnxExit } from './run-rnx'
|
|
12
|
-
import { IS_STANDALONE } from './standalone'
|
|
13
|
+
import { RNX_INTERNAL_COMMAND } from './internal-child'
|
|
13
14
|
|
|
14
15
|
if (process.argv[2] === '__shell-init') {
|
|
15
16
|
const { renderRnxShellInit } = await import('./shell-init')
|
|
@@ -42,643 +43,15 @@ if (process.argv[2] === '__cloud-confirm') {
|
|
|
42
43
|
}
|
|
43
44
|
}
|
|
44
45
|
|
|
45
|
-
//
|
|
46
|
-
//
|
|
47
|
-
//
|
|
48
|
-
//
|
|
49
|
-
// the
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
// don't paper over real bugs.
|
|
57
|
-
function handleTopLevelError(err: unknown): never {
|
|
58
|
-
const msg = err instanceof Error ? err.message : String(err)
|
|
59
|
-
// rnx's known top-level errors are all single-line, narrow strings.
|
|
60
|
-
// everything else gets the full stack so unexpected crashes still surface.
|
|
61
|
-
const isKnownCliFailure =
|
|
62
|
-
/^command timed out after \d+s$/.test(msg) ||
|
|
63
|
-
msg.startsWith('sim disconnected:') ||
|
|
64
|
-
msg.startsWith('bridge never reconnected') ||
|
|
65
|
-
msg.startsWith('multiple sims are connected:') ||
|
|
66
|
-
msg.startsWith('saved sim ') ||
|
|
67
|
-
msg.startsWith('no sim connected with id ') ||
|
|
68
|
-
msg.startsWith('could not connect to ws://') ||
|
|
69
|
-
msg.startsWith('rnx bridge daemon is not running') ||
|
|
70
|
-
msg.startsWith('rnx bridge lockfile is fresh') ||
|
|
71
|
-
msg.startsWith('rnx bridge exited before becoming ready') ||
|
|
72
|
-
msg.startsWith(`${rnxPublicBrand.commandName} open: requested bridge port`) ||
|
|
73
|
-
msg.startsWith('background daemon setup is unavailable') ||
|
|
74
|
-
msg.startsWith('rnx bridge did not start within')
|
|
75
|
-
if (isKnownCliFailure && !process.env.SOOTSIM_VERBOSE) {
|
|
76
|
-
process.stderr.write(` ${msg}\n`)
|
|
77
|
-
process.exit(1)
|
|
78
|
-
}
|
|
79
|
-
if (err instanceof Error && err.stack) {
|
|
80
|
-
process.stderr.write(`${err.stack}\n`)
|
|
81
|
-
} else {
|
|
82
|
-
process.stderr.write(`${msg}\n`)
|
|
83
|
-
}
|
|
84
|
-
process.exit(1)
|
|
85
|
-
}
|
|
86
|
-
process.on('unhandledRejection', handleTopLevelError)
|
|
87
|
-
process.on('uncaughtException', handleTopLevelError)
|
|
88
|
-
|
|
89
|
-
let parsed = parseArgs(process.argv)
|
|
90
|
-
for (const warning of parsed.warnings) process.stderr.write(`${warning}\n`)
|
|
91
|
-
|
|
92
|
-
// runtime actions and reads also work as hidden root aliases. resolve only
|
|
93
|
-
// after the normal parser declined the token, so a real top-level command
|
|
94
|
-
// always wins. keep this lazy so ordinary commands don't load the docs registry.
|
|
95
|
-
if (!parsed.command && parsed.commandArgs.length > 0) {
|
|
96
|
-
const { resolveHiddenRuntimeAlias } = await import('./hidden-runtime-alias')
|
|
97
|
-
parsed = resolveHiddenRuntimeAlias(parsed)
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const isAutomaticCleanupWorker =
|
|
101
|
-
parsed.command === 'cleanup' &&
|
|
102
|
-
parsed.commandArgs.length === 1 &&
|
|
103
|
-
parsed.commandArgs[0] === '--automatic-worker'
|
|
104
|
-
const isCliUpdateWorker =
|
|
105
|
-
parsed.command === 'upgrade' &&
|
|
106
|
-
parsed.commandArgs.length === 1 &&
|
|
107
|
-
parsed.commandArgs[0] === '--check-worker'
|
|
108
|
-
const isBareWelcome = !parsed.command && parsed.commandArgs.length === 0
|
|
109
|
-
const isReadOnlyInvocation =
|
|
110
|
-
parsed.version || parsed.help || parsed.commandArgs.includes('--dry-run')
|
|
111
|
-
|
|
112
|
-
if (isCliUpdateWorker) {
|
|
113
|
-
if (IS_STANDALONE) {
|
|
114
|
-
try {
|
|
115
|
-
const { refreshCliUpdateCache } = await import('./cli-update')
|
|
116
|
-
await refreshCliUpdateCache()
|
|
117
|
-
} catch {}
|
|
118
|
-
}
|
|
119
|
-
process.exit(0)
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
if (
|
|
123
|
-
!isAutomaticCleanupWorker &&
|
|
124
|
-
!isBareWelcome &&
|
|
125
|
-
!parsed.help &&
|
|
126
|
-
!parsed.version &&
|
|
127
|
-
parsed.command !== 'config' &&
|
|
128
|
-
parsed.command !== 'setup' &&
|
|
129
|
-
parsed.command !== 'telemetry'
|
|
130
|
-
) {
|
|
131
|
-
const { ensurePrivacyChoice } = await import('./privacy')
|
|
132
|
-
await ensurePrivacyChoice({ prompt: true })
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// one-shot "engine upgraded in the background" banner. the daemon's hourly
|
|
136
|
-
// auto-updater writes a notice file when it swaps the runtime; whichever
|
|
137
|
-
// interactive command runs next prints it once (stderr, so --json stdout
|
|
138
|
-
// stays parseable) and deletes it. the daemon/serve processes skip it so a
|
|
139
|
-
// background process can't eat the notice before a human sees it.
|
|
140
|
-
if (!isReadOnlyInvocation && parsed.command !== 'serve' && parsed.command !== 'daemon') {
|
|
141
|
-
const { consumeRuntimeUpgradeNotice } = await import('../src/home-paths')
|
|
142
|
-
const notice = consumeRuntimeUpgradeNotice()
|
|
143
|
-
if (notice) {
|
|
144
|
-
process.stderr.write(
|
|
145
|
-
` ${rnxPublicBrand.name} engine upgraded to v${notice.to}` +
|
|
146
|
-
(notice.from ? ` (from v${notice.from})` : '') +
|
|
147
|
-
` · what's new: ${rnxPublicBrand.origin}/changelog\n`,
|
|
148
|
-
)
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
let automaticCleanupFinished = false
|
|
153
|
-
async function spawnDetachedSelf(
|
|
154
|
-
args: string[],
|
|
155
|
-
stderr: 'ignore' | 'inherit',
|
|
156
|
-
): Promise<import('node:child_process').ChildProcess> {
|
|
157
|
-
const { spawn } = await import('node:child_process')
|
|
158
|
-
const options: import('node:child_process').SpawnOptions = {
|
|
159
|
-
detached: true,
|
|
160
|
-
stdio: ['ignore', 'ignore', stderr],
|
|
161
|
-
env: process.env,
|
|
162
|
-
}
|
|
163
|
-
const entry = process.argv[1]
|
|
164
|
-
const child = IS_STANDALONE
|
|
165
|
-
? spawn(process.execPath, args, options)
|
|
166
|
-
: entry
|
|
167
|
-
? spawn(process.execPath, [entry, ...args], options)
|
|
168
|
-
: spawn(rnxPublicBrand.commandName, args, options)
|
|
169
|
-
return child
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
async function scheduleAutomaticCleanup(): Promise<void> {
|
|
173
|
-
if (automaticCleanupFinished) return
|
|
174
|
-
automaticCleanupFinished = true
|
|
175
|
-
if (
|
|
176
|
-
isReadOnlyInvocation ||
|
|
177
|
-
parsed.command === 'cleanup' ||
|
|
178
|
-
parsed.command === 'serve' ||
|
|
179
|
-
parsed.command === 'agent-wrapper' ||
|
|
180
|
-
(parsed.command === 'daemon' && parsed.commandArgs[0] === 'uninstall')
|
|
181
|
-
) {
|
|
182
|
-
return
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
try {
|
|
186
|
-
const { shouldSkipAutomaticSootsimCleanup } = await import('../src/disk-cleanup')
|
|
187
|
-
if (shouldSkipAutomaticSootsimCleanup()) return
|
|
188
|
-
|
|
189
|
-
const workerArgs = ['cleanup', '--automatic-worker']
|
|
190
|
-
const child = await spawnDetachedSelf(workerArgs, 'inherit')
|
|
191
|
-
child.once('error', (error) => {
|
|
192
|
-
process.stderr.write(
|
|
193
|
-
` ${rnxPublicBrand.name} automatic cleanup will retry on the next run: ${error.message}\n`,
|
|
194
|
-
)
|
|
195
|
-
})
|
|
196
|
-
child.unref()
|
|
197
|
-
} catch (error) {
|
|
198
|
-
process.stderr.write(
|
|
199
|
-
` ${rnxPublicBrand.name} automatic cleanup will retry on the next run: ${
|
|
200
|
-
error instanceof Error ? error.message : String(error)
|
|
201
|
-
}\n`,
|
|
202
|
-
)
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
let automaticCliUpdateFinished = false
|
|
207
|
-
async function scheduleAutomaticCliUpdate(exitCode: number): Promise<void> {
|
|
208
|
-
if (automaticCliUpdateFinished) return
|
|
209
|
-
automaticCliUpdateFinished = true
|
|
210
|
-
if (!IS_STANDALONE) return
|
|
211
|
-
const {
|
|
212
|
-
claimAutomaticCliUpdateCheck,
|
|
213
|
-
shouldUseAutomaticCliUpdateCheck,
|
|
214
|
-
takeCliUpdateNotification,
|
|
215
|
-
} = await import('./cli-update')
|
|
216
|
-
if (
|
|
217
|
-
!shouldUseAutomaticCliUpdateCheck({
|
|
218
|
-
command: parsed.command,
|
|
219
|
-
commandArgs: parsed.commandArgs,
|
|
220
|
-
exitCode,
|
|
221
|
-
standalone: IS_STANDALONE,
|
|
222
|
-
stderrIsTTY: process.stderr.isTTY === true,
|
|
223
|
-
stdoutIsTTY: process.stdout.isTTY === true,
|
|
224
|
-
version: parsed.version,
|
|
225
|
-
})
|
|
226
|
-
) {
|
|
227
|
-
return
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
const update = takeCliUpdateNotification()
|
|
231
|
-
if (update) {
|
|
232
|
-
process.stderr.write(
|
|
233
|
-
` update: rnx v${update.currentVersion} → v${update.latestVersion} · run \`rnx upgrade\`\n`,
|
|
234
|
-
)
|
|
235
|
-
}
|
|
236
|
-
if (!claimAutomaticCliUpdateCheck()) return
|
|
237
|
-
try {
|
|
238
|
-
const child = await spawnDetachedSelf(['upgrade', '--check-worker'], 'ignore')
|
|
239
|
-
child.once('error', () => {})
|
|
240
|
-
child.unref()
|
|
241
|
-
} catch {}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
async function exitWithFlush(code: number): Promise<never> {
|
|
245
|
-
const { flushCliTelemetry } = await import('./telemetry')
|
|
246
|
-
await flushCliTelemetry()
|
|
247
|
-
await scheduleAutomaticCliUpdate(code)
|
|
248
|
-
await scheduleAutomaticCleanup()
|
|
249
|
-
process.exit(code)
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
// --version
|
|
253
|
-
if (parsed.version) {
|
|
254
|
-
// use the single canonical, cached version source — the same one
|
|
255
|
-
// `--help`, `upgrade`, the startup flow, and the bridge-host use. it
|
|
256
|
-
// previously re-read package.json independently here, which could
|
|
257
|
-
// resolve a different copy (stale global install vs repo) and drift
|
|
258
|
-
// from `--help` within one session (QA F20-4).
|
|
259
|
-
const { getCliVersion } = await import('../src/cli-version')
|
|
260
|
-
const { IS_BETA, BETA_LABEL } = await import('../src/beta')
|
|
261
|
-
const suffix = IS_BETA ? ` · ${BETA_LABEL}` : ''
|
|
262
|
-
console.log(`${rnxPublicBrand.commandName} v${getCliVersion()}${suffix}`)
|
|
263
|
-
// runtime version on its own line — keeps the first line parseable while
|
|
264
|
-
// surfacing the (independently-versioned) engine runtime users actually
|
|
265
|
-
// render with. see `rnx upgrade`.
|
|
266
|
-
const { readActiveRuntime } = await import('../src/home-paths')
|
|
267
|
-
const runtime = readActiveRuntime()
|
|
268
|
-
console.log(runtime ? `runtime v${runtime}` : 'runtime not installed')
|
|
269
|
-
await exitWithFlush(0)
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
// --help with no command
|
|
273
|
-
if (parsed.help && !parsed.command) {
|
|
274
|
-
const { printHelp } = await import('./help')
|
|
275
|
-
printHelp()
|
|
276
|
-
await exitWithFlush(0)
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
// apply settings from global flags
|
|
280
|
-
const port = parsed.globalFlags['port'] as number | undefined
|
|
281
|
-
const verbose = parsed.verbose
|
|
282
|
-
const device = parsed.globalFlags['device'] as string | undefined
|
|
283
|
-
const theme = parsed.globalFlags['theme'] as string | undefined
|
|
284
|
-
const driver = parsed.globalFlags['driver'] as string | undefined
|
|
285
|
-
const headless = (parsed.globalFlags['headless'] as boolean | undefined) === true
|
|
286
|
-
const globalSimTarget = (parsed.globalFlags['sim'] ??
|
|
287
|
-
parsed.globalFlags['session'] ??
|
|
288
|
-
parsed.globalFlags['tab']) as string | undefined
|
|
289
|
-
const commandArgsWithSim = globalSimTarget
|
|
290
|
-
? ['--sim', globalSimTarget, ...parsed.commandArgs]
|
|
291
|
-
: parsed.commandArgs
|
|
292
|
-
|
|
293
|
-
// apply device/theme to settings store if provided
|
|
294
|
-
if (device || theme) {
|
|
295
|
-
const { settingsStore } = await import('sootsim-engine/settings/store')
|
|
296
|
-
const overrides: Record<string, any> = {}
|
|
297
|
-
if (device) overrides.deviceModel = device
|
|
298
|
-
if (theme) overrides.colorScheme = theme
|
|
299
|
-
settingsStore.apply(overrides)
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
// no command, no args: guide first-time machine setup, then open ConnectRN.
|
|
303
|
-
// returning runs open ConnectRN directly; app discovery belongs inside it.
|
|
304
|
-
if (!parsed.command && parsed.commandArgs.length === 0) {
|
|
305
|
-
const { runWelcome } = await import('./commands/welcome')
|
|
306
|
-
await exitWithFlush(await runWelcome())
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
// route to command. an empty command at this point only happens if the
|
|
310
|
-
// caller passed bare positional args without a verb (e.g. `rnx -- foo`),
|
|
311
|
-
// which used to fall through to the bundler-wrap behavior in `dev`. now that
|
|
312
|
-
// `dev` is gone, treat it as "show help".
|
|
313
|
-
const command = parsed.command ?? ''
|
|
314
|
-
if (!command) {
|
|
315
|
-
// a bare positional that isn't a known command lands here (parse-args
|
|
316
|
-
// leaves `command` null and pushes the token into commandArgs). don't
|
|
317
|
-
// silently dump the full help banner — name the unrecognized token so a
|
|
318
|
-
// typo'd subcommand is obvious. matches the switch `default:` wording.
|
|
319
|
-
if (parsed.commandArgs.length > 0) {
|
|
320
|
-
const attempted =
|
|
321
|
-
parsed.commandArgs.find((a) => !a.startsWith('-')) ?? parsed.commandArgs[0]
|
|
322
|
-
console.error(` unknown command: ${attempted}`)
|
|
323
|
-
console.error(
|
|
324
|
-
` run \`${rnxPublicBrand.commandName} --help\` to see the full surface.`,
|
|
325
|
-
)
|
|
326
|
-
await exitWithFlush(1)
|
|
327
|
-
}
|
|
328
|
-
const { printHelp } = await import('./help')
|
|
329
|
-
printHelp()
|
|
330
|
-
await exitWithFlush(0)
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
// --help with a command (either global flag or in commandArgs)
|
|
334
|
-
if (
|
|
335
|
-
parsed.help ||
|
|
336
|
-
parsed.commandArgs.includes('--help') ||
|
|
337
|
-
parsed.commandArgs.includes('-h')
|
|
338
|
-
) {
|
|
339
|
-
if (command === 'skill') {
|
|
340
|
-
const { runSkill } = await import('./commands/skills')
|
|
341
|
-
await runSkill(parsed.commandArgs)
|
|
342
|
-
await exitWithFlush(0)
|
|
343
|
-
}
|
|
344
|
-
if (command === 'state') {
|
|
345
|
-
const { runState } = await import('./commands/state')
|
|
346
|
-
await exitWithFlush(await runState(commandArgsWithSim, { port }))
|
|
347
|
-
}
|
|
348
|
-
if (command === 'reset') {
|
|
349
|
-
const { runReset } = await import('./commands/reset')
|
|
350
|
-
await exitWithFlush(await runReset(commandArgsWithSim, { port }))
|
|
351
|
-
}
|
|
352
|
-
// for grouping verbs, try to resolve per-verb help from the first
|
|
353
|
-
// positional arg. e.g. `rnx do tap --help` shows tap's help page,
|
|
354
|
-
// not the generic `do` grouping page. bare `rnx do --help` falls
|
|
355
|
-
// through to runInspect which has the full verb group listing.
|
|
356
|
-
const isGroupingVerb =
|
|
357
|
-
command === 'do' ||
|
|
358
|
-
command === 'get' ||
|
|
359
|
-
command === 'debug' ||
|
|
360
|
-
command === 'shell' ||
|
|
361
|
-
command === 'perf' ||
|
|
362
|
-
command === 'wait'
|
|
363
|
-
const subVerb = parsed.commandArgs.find((a) => !a.startsWith('-'))
|
|
364
|
-
if (command === 'shell' || command === 'perf') {
|
|
365
|
-
// shell and perf subcommands are documented by their runtime dispatchers.
|
|
366
|
-
// don't try to route through generated per-verb docs.
|
|
367
|
-
} else if (isGroupingVerb && !subVerb) {
|
|
368
|
-
// bare `rnx <group> --help` — render from the registry.
|
|
369
|
-
const { printGroupHelp } = await import('./help')
|
|
370
|
-
if (printGroupHelp(command)) await exitWithFlush(0)
|
|
371
|
-
// fall through — runInspect shows the full help for anything the
|
|
372
|
-
// registry doesn't yet cover (e.g. shell).
|
|
373
|
-
} else {
|
|
374
|
-
const { printCommandHelp } = await import('./help')
|
|
375
|
-
const helpName = isGroupingVerb && subVerb ? subVerb : command
|
|
376
|
-
printCommandHelp(helpName, {
|
|
377
|
-
prefer: isGroupingVerb && subVerb ? 'verb' : 'command',
|
|
378
|
-
group: isGroupingVerb && subVerb ? command : undefined,
|
|
379
|
-
})
|
|
380
|
-
await exitWithFlush(0)
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
const { dispatchCloudBoundary } = await import('./cloud-dispatch')
|
|
385
|
-
const cloudBoundary = await dispatchCloudBoundary({
|
|
386
|
-
command,
|
|
387
|
-
args: commandArgsWithSim,
|
|
388
|
-
})
|
|
389
|
-
if (cloudBoundary.handled) await exitWithFlush(cloudBoundary.code)
|
|
390
|
-
// the command modules stop by throwing RnxExit rather than ending the
|
|
391
|
-
// process, because the same command has to run in a box shell and in
|
|
392
|
-
// workerd, where process.exit() cancels the whole request. this is the
|
|
393
|
-
// host boundary that turns it back into a real exit code.
|
|
394
|
-
try {
|
|
395
|
-
if (TOP_LEVEL_RUNTIME_COMMANDS.has(command)) {
|
|
396
|
-
if (command === 'do') {
|
|
397
|
-
const { hasDoChain, runDoChain } = await import('./commands/do-chain')
|
|
398
|
-
if (hasDoChain(commandArgsWithSim)) {
|
|
399
|
-
await exitWithFlush(await runDoChain(commandArgsWithSim, { port }))
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
const { runInspect } = await import('./commands/inspect')
|
|
403
|
-
await runInspect([command, ...commandArgsWithSim], { port, verbose })
|
|
404
|
-
} else {
|
|
405
|
-
switch (command) {
|
|
406
|
-
case 'assert': {
|
|
407
|
-
// stays outside the usual dispatcher — it spawns self to get the
|
|
408
|
-
// inner verb's --json payload, so it never needs the shared bridge
|
|
409
|
-
// machinery that lives inside runInspect.
|
|
410
|
-
const { runAssert } = await import('./commands/assert')
|
|
411
|
-
await runAssert(parsed.commandArgs)
|
|
412
|
-
break
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
case 'detox': {
|
|
416
|
-
const { runDetox } = await import('./commands/detox')
|
|
417
|
-
await runDetox(parsed.commandArgs, { port, verbose })
|
|
418
|
-
break
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
case 'maestro': {
|
|
422
|
-
const { runMaestro } = await import('./commands/maestro')
|
|
423
|
-
// Maestro forwards playback argv to the shared runner, so it must
|
|
424
|
-
// receive the global `--sim`. passing the bare
|
|
425
|
-
// parsed.commandArgs dropped an explicit `rnx --sim <id> maestro
|
|
426
|
-
// …`, silently falling back to the current/saved sim (QA F21-4).
|
|
427
|
-
const code = await runMaestro(commandArgsWithSim, { port, verbose })
|
|
428
|
-
// bridge + dynamic imports leave handles open that keep node alive.
|
|
429
|
-
await exitWithFlush(typeof code === 'number' ? code : 0)
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
case 'record': {
|
|
433
|
-
const { runRecord } = await import('./commands/record')
|
|
434
|
-
await runRecord(commandArgsWithSim, { port, verbose })
|
|
435
|
-
break
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
case 'film': {
|
|
439
|
-
const { runFilm } = await import('./commands/film')
|
|
440
|
-
// film chains the flow runner, which opens ws bridges + dynamic
|
|
441
|
-
// imports that keep node alive — exit explicitly like `flow` does.
|
|
442
|
-
const code = await runFilm(commandArgsWithSim, { port, verbose })
|
|
443
|
-
await exitWithFlush(typeof code === 'number' ? code : 0)
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
case 'storage': {
|
|
447
|
-
const { runStorage } = await import('./commands/storage')
|
|
448
|
-
const code = await runStorage(commandArgsWithSim, { port, verbose })
|
|
449
|
-
await exitWithFlush(typeof code === 'number' ? code : 0)
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
case 'state': {
|
|
453
|
-
const { runState } = await import('./commands/state')
|
|
454
|
-
const code = await runState(commandArgsWithSim, { port })
|
|
455
|
-
await exitWithFlush(code)
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
case 'reset': {
|
|
459
|
-
const { runReset } = await import('./commands/reset')
|
|
460
|
-
const code = await runReset(commandArgsWithSim, { port })
|
|
461
|
-
await exitWithFlush(code)
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
case 'perf': {
|
|
465
|
-
const { runPerf } = await import('./commands/perf')
|
|
466
|
-
const code = await runPerf(commandArgsWithSim, { port, verbose })
|
|
467
|
-
await exitWithFlush(typeof code === 'number' ? code : 0)
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
case 'screenshot': {
|
|
471
|
-
const { runScreenshotCommand } = await import('./commands/screenshot-command')
|
|
472
|
-
const code = await runScreenshotCommand(commandArgsWithSim, { port, verbose })
|
|
473
|
-
await exitWithFlush(typeof code === 'number' ? code : 0)
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
case 'camera': {
|
|
477
|
-
const { runCamera } = await import('./commands/camera')
|
|
478
|
-
const code = await runCamera(commandArgsWithSim, { port })
|
|
479
|
-
await exitWithFlush(typeof code === 'number' ? code : 0)
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
case 'mode': {
|
|
483
|
-
const { runMode } = await import('./commands/mode')
|
|
484
|
-
await runMode(commandArgsWithSim, { port, verbose })
|
|
485
|
-
break
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
case 'permissions': {
|
|
489
|
-
const { runPermissions } = await import('./commands/permissions')
|
|
490
|
-
const code = await runPermissions(commandArgsWithSim, { port, verbose })
|
|
491
|
-
await exitWithFlush(code)
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
case 'debug': {
|
|
495
|
-
const { runDebug } = await import('./commands/debug')
|
|
496
|
-
await runDebug(commandArgsWithSim, { port, verbose })
|
|
497
|
-
break
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
case 'timeline': {
|
|
501
|
-
const { runTimeline } = await import('./commands/timeline')
|
|
502
|
-
await runTimeline(commandArgsWithSim, { port, verbose })
|
|
503
|
-
break
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
case 'what-happened': {
|
|
507
|
-
const { runWhatHappened } = await import('./commands/what-happened')
|
|
508
|
-
await runWhatHappened(commandArgsWithSim, { port, verbose })
|
|
509
|
-
break
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
case 'open': {
|
|
513
|
-
const { runOpenCommand } = await import('./commands/control')
|
|
514
|
-
await runOpenCommand(commandArgsWithSim, { port })
|
|
515
|
-
break
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
case 'ios':
|
|
519
|
-
case 'android': {
|
|
520
|
-
const { runPlatformCommand } = await import('./commands/platform')
|
|
521
|
-
const code = await runPlatformCommand(command, commandArgsWithSim, { port })
|
|
522
|
-
await exitWithFlush(typeof code === 'number' ? code : 0)
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
case 'web': {
|
|
526
|
-
const { getRnxCommandAvailability } = await import('./command-registry')
|
|
527
|
-
const availability = getRnxCommandAvailability('web', parsed.commandArgs, 'node')
|
|
528
|
-
console.error(` ${availability.reason ?? 'rnx web is unavailable'}`)
|
|
529
|
-
await exitWithFlush(1)
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
case 'box': {
|
|
533
|
-
const { runBox } = await import('./commands/box')
|
|
534
|
-
await exitWithFlush(await runBox(commandArgsWithSim, { port, verbose }))
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
case 'use': {
|
|
538
|
-
const { runUseCommand } = await import('./commands/control')
|
|
539
|
-
await runUseCommand(commandArgsWithSim, { port })
|
|
540
|
-
break
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
case 'claim': {
|
|
544
|
-
const { runClaimCommand } = await import('./commands/control')
|
|
545
|
-
await runClaimCommand(commandArgsWithSim, { port })
|
|
546
|
-
break
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
case 'close': {
|
|
550
|
-
const { runCloseCommand } = await import('./commands/control')
|
|
551
|
-
await runCloseCommand(commandArgsWithSim, { port })
|
|
552
|
-
break
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
case 'device': {
|
|
556
|
-
const { runDeviceCommand } = await import('./commands/device')
|
|
557
|
-
await runDeviceCommand(commandArgsWithSim, { port })
|
|
558
|
-
break
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
case 'compat': {
|
|
562
|
-
const { runCompat } = await import('./commands/compat')
|
|
563
|
-
await runCompat(parsed.commandArgs)
|
|
564
|
-
break
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
case 'report-issue': {
|
|
568
|
-
const { runReportIssue } = await import('./commands/report-issue')
|
|
569
|
-
const code = await runReportIssue(parsed.commandArgs)
|
|
570
|
-
await exitWithFlush(code)
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
case 'desktop': {
|
|
574
|
-
const { runDesktop } = await import('./commands/desktop')
|
|
575
|
-
await runDesktop(parsed.commandArgs, { port, device })
|
|
576
|
-
break
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
case 'login': {
|
|
580
|
-
const { runLogin } = await import('./commands/login')
|
|
581
|
-
await runLogin(parsed.commandArgs)
|
|
582
|
-
break
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
case 'logout': {
|
|
586
|
-
const { runLogout } = await import('./commands/logout')
|
|
587
|
-
await runLogout()
|
|
588
|
-
break
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
case 'auth': {
|
|
592
|
-
const { runAuth } = await import('./commands/auth')
|
|
593
|
-
await runAuth(parsed.commandArgs)
|
|
594
|
-
break
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
case 'setup': {
|
|
598
|
-
const { runSetup } = await import('./commands/setup')
|
|
599
|
-
await runSetup(parsed.commandArgs)
|
|
600
|
-
break
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
case 'serve': {
|
|
604
|
-
const { runServe } = await import('./commands/serve')
|
|
605
|
-
await runServe(parsed.commandArgs, { port })
|
|
606
|
-
break
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
case 'daemon': {
|
|
610
|
-
const { runDaemon } = await import('./commands/daemon')
|
|
611
|
-
await runDaemon(parsed.commandArgs, { port })
|
|
612
|
-
break
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
case 'runtime': {
|
|
616
|
-
const { runRuntime } = await import('./commands/runtime')
|
|
617
|
-
await runRuntime(parsed.commandArgs)
|
|
618
|
-
break
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
case 'upgrade': {
|
|
622
|
-
const { runUpgrade } = await import('./commands/upgrade')
|
|
623
|
-
await runUpgrade(parsed.commandArgs)
|
|
624
|
-
break
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
case 'version': {
|
|
628
|
-
const { runVersion } = await import('./commands/version')
|
|
629
|
-
await runVersion(parsed.commandArgs)
|
|
630
|
-
break
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
case 'agent': {
|
|
634
|
-
const { runAgentCommand } = await import('./commands/agent')
|
|
635
|
-
const code = await runAgentCommand(parsed.commandArgs)
|
|
636
|
-
await exitWithFlush(code)
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
case 'agent-wrapper': {
|
|
640
|
-
const { runAgentWrapper } = await import('./commands/agent-wrapper')
|
|
641
|
-
const code = await runAgentWrapper(parsed.commandArgs)
|
|
642
|
-
await exitWithFlush(code)
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
case 'skill': {
|
|
646
|
-
const { runSkill } = await import('./commands/skills')
|
|
647
|
-
await runSkill(parsed.commandArgs)
|
|
648
|
-
break
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
case 'app-fonts': {
|
|
652
|
-
const { runAppFonts } = await import('./commands/app-fonts')
|
|
653
|
-
await runAppFonts(parsed.commandArgs)
|
|
654
|
-
break
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
case 'config': {
|
|
658
|
-
const { runConfig } = await import('./commands/config')
|
|
659
|
-
await runConfig(parsed.commandArgs)
|
|
660
|
-
break
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
case 'cleanup': {
|
|
664
|
-
const { runCleanup } = await import('./commands/cleanup')
|
|
665
|
-
const code = await runCleanup(parsed.commandArgs)
|
|
666
|
-
await exitWithFlush(code)
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
default: {
|
|
670
|
-
console.error(` unknown command: ${command}`)
|
|
671
|
-
console.error(
|
|
672
|
-
` run \`${rnxPublicBrand.commandName} --help\` to see the full surface.`,
|
|
673
|
-
)
|
|
674
|
-
await exitWithFlush(1)
|
|
675
|
-
}
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
|
-
} catch (error) {
|
|
679
|
-
if (error instanceof RnxExit) await exitWithFlush(error.code)
|
|
680
|
-
throw error
|
|
46
|
+
// child processes this CLI spawns as itself: the detached browser host,
|
|
47
|
+
// playwright's installer, maestro's blocking fetch worker. unlike the two
|
|
48
|
+
// above, these do not exit on the way out — the installer and the browser host
|
|
49
|
+
// are still working when their handler returns, and the process ends when that
|
|
50
|
+
// work does. so this is the branch that must skip the CLI rather than the CLI
|
|
51
|
+
// that must skip it.
|
|
52
|
+
if (process.argv[2] === RNX_INTERNAL_COMMAND) {
|
|
53
|
+
const { runInternalChild } = await import('./internal-child')
|
|
54
|
+
await runInternalChild(process.argv.slice(3))
|
|
55
|
+
} else {
|
|
56
|
+
await import('./main')
|
|
681
57
|
}
|
|
682
|
-
|
|
683
|
-
await scheduleAutomaticCliUpdate(0)
|
|
684
|
-
await scheduleAutomaticCleanup()
|