rnxsim 0.1.408 → 0.1.410

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/README.md +11 -10
  2. package/cli/bin.ts +16 -643
  3. package/cli/commands/assert.ts +6 -6
  4. package/cli/commands/box/rnx-command.ts +3 -11
  5. package/cli/commands/control.ts +3 -9
  6. package/cli/commands/daemon-mac-app.ts +5 -11
  7. package/cli/commands/daemon.ts +63 -113
  8. package/cli/drivers/playwright-provisioning.ts +33 -29
  9. package/cli/drivers/playwright-sim-host.ts +501 -0
  10. package/cli/drivers/playwright.ts +35 -522
  11. package/cli/hints.ts +2 -4
  12. package/cli/internal-child.ts +176 -0
  13. package/cli/maestro-js.ts +28 -39
  14. package/cli/main.ts +645 -0
  15. package/cli/outbound-endpoints.ts +8 -0
  16. package/cli/self-invocation.ts +34 -0
  17. package/dist-lib/agent-daemon-client.cjs +35 -25
  18. package/dist-lib/agent-events.cjs +1 -1
  19. package/dist-lib/agent-identity.cjs +1 -1
  20. package/dist-lib/agent-sessions.cjs +35 -25
  21. package/dist-lib/attached-projects.cjs +1 -1
  22. package/dist-lib/auth/shared-session.cjs +1 -1
  23. package/dist-lib/backend-origin.cjs +1 -1
  24. package/dist-lib/beta.cjs +1 -1
  25. package/dist-lib/beta.mjs +1 -1
  26. package/dist-lib/bridge-constants.cjs +1 -1
  27. package/dist-lib/bridge-contract-input.cjs +1 -1
  28. package/dist-lib/bridge-contract-input.mjs +1 -1
  29. package/dist-lib/bridge-contract.cjs +1 -1
  30. package/dist-lib/bridge-contract.mjs +1 -1
  31. package/dist-lib/capture-contract.cjs +1 -1
  32. package/dist-lib/capture-contract.mjs +1 -1
  33. package/dist-lib/cli-constants.cjs +1 -1
  34. package/dist-lib/cloud-contract.cjs +1 -4
  35. package/dist-lib/cloud-contract.mjs +1 -3
  36. package/dist-lib/config.cjs +1 -1
  37. package/dist-lib/detox/index.cjs +1 -1
  38. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  39. package/dist-lib/home-paths.cjs +1 -1
  40. package/dist-lib/host/bridge-host.cjs +25 -15
  41. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  42. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  43. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  44. package/dist-lib/host/replacement-module-handler.cjs +1 -1
  45. package/dist-lib/host/websocket-proxy.cjs +1 -1
  46. package/dist-lib/index.cjs +1 -1
  47. package/dist-lib/jump-to-source-babel.cjs +1 -1
  48. package/dist-lib/menu.cjs +1 -1
  49. package/dist-lib/menu.mjs +1 -1
  50. package/dist-lib/metro-fingerprint-registry.cjs +1 -1
  51. package/dist-lib/metro-fingerprint-registry.mjs +1 -1
  52. package/dist-lib/metro-production-bundle.cjs +1 -1
  53. package/dist-lib/metro-production-bundle.mjs +1 -1
  54. package/dist-lib/metro.cjs +1 -1
  55. package/dist-lib/profiles.cjs +1 -1
  56. package/dist-lib/public-brand.cjs +1 -1
  57. package/dist-lib/react-native-host-modules.cjs +1 -1
  58. package/dist-lib/react-native-host-modules.mjs +1 -1
  59. package/dist-lib/render-mode.cjs +1 -1
  60. package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
  61. package/dist-lib/sdk.cjs +1 -1
  62. package/dist-lib/sdk.mjs +1 -1
  63. package/dist-lib/skills.cjs +134 -608
  64. package/dist-lib/vite.cjs +1 -1
  65. package/package.json +1 -1
  66. package/src/agent-daemon-client.ts +2 -2
  67. package/src/agent-sessions.ts +29 -24
  68. package/src/cloud-contract.ts +0 -1
  69. 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 { rnxPublicBrand } from '../src/public-brand'
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
- // `./help` is loaded lazily (only on help paths) it statically pulls the full
46
- // CLI registry renderers and engine settings, which would otherwise add ~400ms
47
- // of eager chunk load to every command, including hot-path reads like
48
- // `describe` / `do tap` that never render help. parse-args has no imports, so
49
- // the startup floor stays at the runtime's own cost (~15ms).
50
-
51
- // keep top-level dispatch crashes readable. without this, a 10s WS bridge
52
- // timeout (the most common failure when a sim is dead) prints a 1KB minified
53
- // chunk of the bundled CLI source straight to stderr, which buries the actual
54
- // "command timed out after 10s" message and is uncopyable for a bug report.
55
- // rejections that don't match this shape still get the verbose stack so we
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()