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.
Files changed (138) hide show
  1. package/README.md +43 -26
  2. package/cli/app-fonts.ts +1 -1
  3. package/cli/bin.ts +118 -26
  4. package/cli/bridge-diagnostics.ts +1 -1
  5. package/cli/bridge-flow-runner.ts +9 -9
  6. package/cli/browser-evals.ts +3 -3
  7. package/cli/cli-update.ts +261 -0
  8. package/cli/cloud-client.ts +407 -0
  9. package/cli/cloud-dispatch.ts +165 -0
  10. package/cli/cloud-session.ts +156 -0
  11. package/cli/command-contract.ts +2 -2
  12. package/cli/command-registry.ts +9 -0
  13. package/cli/commands/agent-wrapper.ts +4 -4
  14. package/cli/commands/agent.ts +3 -3
  15. package/cli/commands/box/checkout-plane.ts +16 -8
  16. package/cli/commands/box/rnx-command.ts +2 -2
  17. package/cli/commands/box/search-command.ts +2 -2
  18. package/cli/commands/box.ts +123 -70
  19. package/cli/commands/config.ts +3 -2
  20. package/cli/commands/control.ts +6 -6
  21. package/cli/commands/cpu-profile.ts +2 -2
  22. package/cli/commands/daemon.ts +7 -7
  23. package/cli/commands/debug.ts +2 -2
  24. package/cli/commands/detox.ts +7 -7
  25. package/cli/commands/diagnose.ts +1 -1
  26. package/cli/commands/electron.ts +1 -1
  27. package/cli/commands/film.ts +1 -1
  28. package/cli/commands/flow.ts +4 -4
  29. package/cli/commands/inspect/core.ts +58 -7
  30. package/cli/commands/inspect/env.ts +5 -3
  31. package/cli/commands/inspect/shared.ts +2 -1
  32. package/cli/commands/inspect/wait-event.ts +1 -1
  33. package/cli/commands/inspect/wait-ready.ts +15 -5
  34. package/cli/commands/inspect.ts +31 -9
  35. package/cli/commands/install-desktop.ts +2 -2
  36. package/cli/commands/maestro.ts +3 -3
  37. package/cli/commands/permissions.ts +1 -1
  38. package/cli/commands/platform.ts +91 -0
  39. package/cli/commands/record.ts +3 -3
  40. package/cli/commands/report-issue.ts +1 -1
  41. package/cli/commands/runtime.ts +1 -1
  42. package/cli/commands/screenshot-mode.ts +1 -1
  43. package/cli/commands/screenshot.ts +25 -0
  44. package/cli/commands/serve.ts +1 -1
  45. package/cli/commands/skills.ts +1 -1
  46. package/cli/commands/timeline.ts +1 -1
  47. package/cli/commands/upgrade.ts +8 -34
  48. package/cli/commands/upload.ts +3 -3
  49. package/cli/commands/version.ts +14 -3
  50. package/cli/commands/what-happened.ts +2 -2
  51. package/cli/current-sim.ts +43 -30
  52. package/cli/desktop-companion.ts +1 -1
  53. package/cli/drivers/electron.ts +1 -1
  54. package/cli/drivers/playwright.ts +3 -3
  55. package/cli/drivers/types.ts +4 -4
  56. package/cli/flow-file.ts +3 -3
  57. package/cli/help-core.ts +18 -11
  58. package/cli/hidden-runtime-alias.ts +1 -1
  59. package/cli/hints.ts +2 -2
  60. package/cli/maestro-js.ts +2 -2
  61. package/cli/outbound-endpoints.ts +40 -6
  62. package/cli/parse-args.ts +1 -1
  63. package/cli/registry.ts +1 -1
  64. package/cli/resolve-assets.ts +2 -2
  65. package/cli/secure-file.ts +31 -0
  66. package/cli/shell-init.ts +98 -0
  67. package/cli/ws-bridge.ts +18 -8
  68. package/detox/expectations.ts +1 -1
  69. package/detox/gestures.ts +3 -3
  70. package/detox/index.ts +3 -3
  71. package/dist-lib/agent-daemon-client.cjs +1 -1
  72. package/dist-lib/agent-events.cjs +1 -1
  73. package/dist-lib/agent-identity.cjs +1 -1
  74. package/dist-lib/agent-sessions.cjs +1 -1
  75. package/dist-lib/attached-projects.cjs +1 -1
  76. package/dist-lib/auth/shared-session.cjs +1 -1
  77. package/dist-lib/backend-origin.cjs +1 -1
  78. package/dist-lib/beta.cjs +1 -1
  79. package/dist-lib/beta.mjs +1 -1
  80. package/dist-lib/bridge-constants.cjs +1 -1
  81. package/dist-lib/bridge-contract-input.cjs +15 -8
  82. package/dist-lib/bridge-contract-input.mjs +15 -8
  83. package/dist-lib/bridge-contract.cjs +1 -1
  84. package/dist-lib/bridge-contract.mjs +1 -1
  85. package/dist-lib/capture-contract.cjs +107 -0
  86. package/dist-lib/capture-contract.mjs +80 -0
  87. package/dist-lib/cli-constants.cjs +1 -1
  88. package/dist-lib/cloud-contract.cjs +32 -0
  89. package/dist-lib/cloud-contract.mjs +7 -0
  90. package/dist-lib/config.cjs +1 -1
  91. package/dist-lib/detox/index.cjs +4 -4
  92. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  93. package/dist-lib/home-paths.cjs +9 -1
  94. package/dist-lib/host/bridge-host.cjs +15 -10
  95. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  96. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  97. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  98. package/dist-lib/host/replacement-module-handler.cjs +1 -1
  99. package/dist-lib/host/websocket-proxy.cjs +1 -1
  100. package/dist-lib/index.cjs +248 -73
  101. package/dist-lib/jump-to-source-babel.cjs +1 -1
  102. package/dist-lib/menu.cjs +1 -1
  103. package/dist-lib/menu.mjs +1 -1
  104. package/dist-lib/metro-production-bundle.cjs +1 -1
  105. package/dist-lib/metro-production-bundle.mjs +1 -1
  106. package/dist-lib/metro.cjs +1 -1
  107. package/dist-lib/profiles.cjs +1 -1
  108. package/dist-lib/public-brand.cjs +1 -1
  109. package/dist-lib/react-native-host-modules.cjs +1 -1
  110. package/dist-lib/react-native-host-modules.mjs +1 -1
  111. package/dist-lib/render-mode.cjs +1 -1
  112. package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
  113. package/dist-lib/sdk.cjs +44 -6
  114. package/dist-lib/sdk.mjs +44 -6
  115. package/dist-lib/skills.cjs +435 -155
  116. package/dist-lib/vite.cjs +1 -1
  117. package/package.json +21 -5
  118. package/scripts/dev-server-scanner.ts +3 -3
  119. package/skills/contrast/SKILL.md +1 -1
  120. package/skills/rnx-perf/SKILL.md +68 -52
  121. package/src/app-url.ts +2 -3
  122. package/src/bridge-contract-input.ts +17 -6
  123. package/src/bridge-contract.ts +25 -4
  124. package/src/capture-contract.ts +423 -0
  125. package/src/cloud-contract.ts +15 -0
  126. package/src/home-paths.ts +9 -3
  127. package/src/host/agent-host.ts +1 -1
  128. package/src/host/bridge-host.ts +13 -11
  129. package/src/host/plane-host.ts +2 -2
  130. package/src/index.ts +21 -0
  131. package/src/native-dev-bundle-url.ts +2 -2
  132. package/src/screenshots/orchestrate.ts +1 -1
  133. package/src/screenshots/org-deck.ts +1 -1
  134. package/src/settings-bundle.ts +1 -1
  135. package/src/sim-client.ts +4 -0
  136. package/src/vite-plugin-one.ts +2 -2
  137. package/src/vite-plugin.ts +1 -1
  138. package/cli/commands/box/cloud-box.ts +0 -294
@@ -1,48 +1,40 @@
1
1
  // `rnx box` — the box environment.
2
2
  //
3
- // bare `rnx box` is LOCAL and mounts the current checkout at /project. `rnx box
4
- // create` provisions a durable cloud box. everything else in the namespace is
5
- // cloud lifecycle. the cloud side is not built yet; those verbs fail by name
6
- // and say what works instead, so nothing advertises a capability that always
7
- // fails.
3
+ // bare `rnx box` is LOCAL and mounts the current checkout at /project. cloud
4
+ // create provisions through the shared client and enters the same shell unless
5
+ // --json asks for a noninteractive result. reconnect still needs an authorized
6
+ // provider lookup from public box identity to the provider reference.
8
7
 
9
8
  import path from 'node:path'
10
9
  import { createInterface } from 'node:readline'
11
- import {
12
- createHarnessShell,
13
- HarnessProjectFs,
14
- PROJECT_MOUNT,
15
- } from '@contrast/harness/shell'
10
+ import { BoxClientError, createBoxClient } from '@rnx/box/client'
11
+ import { createBoxHostProvider } from '@rnx/box/host'
12
+ import { PROJECT_MOUNT } from '@rnx/box/shell'
13
+ import { terminalBoxStack } from '@rnx/box/stack'
14
+ import { BoxTemplateRegistry, terminalBoxTemplate } from '@rnx/box/template'
15
+ import { createCloudBoxProvider, heartbeatSession } from 'rnx-cloud-box/client'
16
16
  import { rnxPublicBrand } from '../../src/public-brand'
17
17
  import { authHeaderOrExit } from '../auth'
18
18
  import { findRepositoryPackageRoot } from '../setup-repository'
19
19
  import { resolvePlacement } from '../ws-bridge'
20
20
  import { CheckoutFilePlane, CheckoutRequiresGitError } from './box/checkout-plane'
21
- import {
22
- CloudBoxError,
23
- connectCloudBox,
24
- createCloudBox,
25
- heartbeatSession,
26
- listCloudBoxes,
27
- stopCloudBox,
28
- } from './box/cloud-box'
29
21
  import { sharePlane } from './box/plane-share'
30
22
  import { createRnxCommand } from './box/rnx-command'
31
23
  import { createSearchCommand } from './box/search-command'
32
- import type { HarnessShellResult } from '@contrast/harness/shell'
24
+ import type { BoxShellSession } from '@rnx/box/client'
25
+ import type { BoxTemplateResolution } from '@rnx/box/template'
33
26
 
34
27
  interface BoxCommandOptions {
35
28
  port?: number
36
29
  verbose?: boolean
37
30
  }
38
31
 
39
- const CLOUD_SUBCOMMANDS = new Set(['connect'])
40
-
41
32
  // no default. a cloud box service has to be deployed before one can exist, and
42
33
  // defaulting to an origin that answers nothing would advertise a capability
43
34
  // that always fails.
44
35
  const ENDPOINT_ENV = 'RNX_BOX_ENDPOINT'
45
36
  const API_ORIGIN_ENV = 'RNX_API_ORIGIN'
37
+ const BOX_TEMPLATES = new BoxTemplateRegistry([terminalBoxTemplate])
46
38
 
47
39
  function usage(): void {
48
40
  const rnx = rnxPublicBrand.commandName
@@ -53,9 +45,12 @@ function usage(): void {
53
45
  ${rnx} box start a local box over the current checkout
54
46
  ${rnx} box create create a cloud box and enter its shell
55
47
  ${rnx} box create --name=<n> create a named cloud box
48
+ ${rnx} box create --template=<id>
49
+ create from a registered Box template
56
50
  ${rnx} box create --endpoint=<url>
57
51
  the box service to create it in, or set
58
52
  ${ENDPOINT_ENV}
53
+ ${rnx} box create --json create without entering the shell
59
54
  ${rnx} box ls cloud boxes running on this account
60
55
  ${rnx} box stop --name=<n> stop a cloud box and its billing
61
56
  ${rnx} box delete --name=<n> stop it and wipe its files
@@ -89,18 +84,18 @@ export async function runBox(
89
84
 
90
85
  const subcommand = args.find((arg) => !arg.startsWith('-'))
91
86
  if (subcommand === 'create') return runCloudBoxCreate(args, opts)
87
+ if (subcommand === 'connect') {
88
+ console.error(
89
+ ` ${rnxPublicBrand.commandName} box connect: the cloud provider cannot yet resolve a Box ID or name to an authorized Box reference.\n` +
90
+ ` the shared Box client requires that reference's access token, and the CLI does not persist Box tokens.`,
91
+ )
92
+ return 1
93
+ }
92
94
  if (subcommand === 'stop' || subcommand === 'delete') {
93
95
  return runCloudBoxStop(args, subcommand === 'delete')
94
96
  }
95
97
  if (subcommand === 'ls') return runCloudBoxList(args)
96
98
  if (subcommand !== undefined) {
97
- if (CLOUD_SUBCOMMANDS.has(subcommand)) {
98
- console.error(
99
- ` ${rnxPublicBrand.commandName} box ${subcommand} needs a cloud box, which is not built yet.\n` +
100
- ` \`${rnxPublicBrand.commandName} box\` runs the same shell locally over this checkout.`,
101
- )
102
- return 1
103
- }
104
99
  console.error(
105
100
  ` unknown command: ${rnxPublicBrand.commandName} box ${subcommand}\n` +
106
101
  ` run \`${rnxPublicBrand.commandName} box --help\` to see the box surface.`,
@@ -108,6 +103,13 @@ export async function runBox(
108
103
  return 1
109
104
  }
110
105
 
106
+ if (args.includes('--json')) {
107
+ console.error(
108
+ ` ${rnxPublicBrand.commandName} box --json requires a noninteractive cloud subcommand such as create.`,
109
+ )
110
+ return 1
111
+ }
112
+
111
113
  return runLocalBox(opts)
112
114
  }
113
115
 
@@ -152,13 +154,14 @@ async function runCloudBoxStop(args: string[], purge: boolean): Promise<number>
152
154
  return 1
153
155
  }
154
156
  const { header } = authHeaderOrExit(`box ${label}`)
157
+ const apiKey = header.replace(/^Bearer /, '')
158
+ const client = createBoxClient(
159
+ createCloudBoxProvider({ endpoint, apiOrigin: apiOrigin(args), apiKey }),
160
+ )
155
161
  try {
156
- const result = await stopCloudBox({
157
- endpoint,
158
- name,
159
- apiKey: header.replace(/^Bearer /, ''),
160
- purge,
161
- })
162
+ const result = purge
163
+ ? await client.delete({ boxId: name })
164
+ : await client.stop({ boxId: name })
162
165
  console.log(
163
166
  purge
164
167
  ? ` deleted ${name}, freeing ${Number(result.freedBytes ?? 0)} bytes`
@@ -166,7 +169,7 @@ async function runCloudBoxStop(args: string[], purge: boolean): Promise<number>
166
169
  )
167
170
  return 0
168
171
  } catch (error) {
169
- if (error instanceof CloudBoxError) {
172
+ if (error instanceof BoxClientError) {
170
173
  console.error(` ${rnx} box ${label}: ${error.message}`)
171
174
  return 1
172
175
  }
@@ -177,22 +180,29 @@ async function runCloudBoxStop(args: string[], purge: boolean): Promise<number>
177
180
  async function runCloudBoxList(args: string[]): Promise<number> {
178
181
  const rnx = rnxPublicBrand.commandName
179
182
  const { header } = authHeaderOrExit('box ls')
180
- try {
181
- const boxes = await listCloudBoxes({
183
+ const apiKey = header.replace(/^Bearer /, '')
184
+ const client = createBoxClient(
185
+ createCloudBoxProvider({
186
+ endpoint: flag(args, 'endpoint') ?? process.env[ENDPOINT_ENV]?.trim() ?? '',
182
187
  apiOrigin: apiOrigin(args),
183
- apiKey: header.replace(/^Bearer /, ''),
184
- })
188
+ apiKey,
189
+ }),
190
+ )
191
+ try {
192
+ const boxes = await client.list()
185
193
  if (boxes.length === 0) {
186
194
  console.log(` no cloud boxes running on this account`)
187
195
  return 0
188
196
  }
189
197
  for (const box of boxes) {
190
198
  const minutes = Math.round(box.meteredMs / 60_000)
191
- console.log(` ${box.name ?? box.id} running ${minutes}m since ${box.startedAt}`)
199
+ console.log(
200
+ ` ${box.name ?? box.boxId} running ${minutes}m since ${box.startedAt}`,
201
+ )
192
202
  }
193
203
  return 0
194
204
  } catch (error) {
195
- if (error instanceof CloudBoxError) {
205
+ if (error instanceof BoxClientError) {
196
206
  console.error(` ${rnx} box ls: ${error.message}`)
197
207
  return 1
198
208
  }
@@ -205,6 +215,7 @@ async function runCloudBoxCreate(
205
215
  opts: BoxCommandOptions,
206
216
  ): Promise<number> {
207
217
  const rnx = rnxPublicBrand.commandName
218
+ const json = args.includes('--json')
208
219
  const endpoint = flag(args, 'endpoint') ?? process.env[ENDPOINT_ENV]?.trim()
209
220
  if (!endpoint) {
210
221
  console.error(
@@ -215,6 +226,17 @@ async function runCloudBoxCreate(
215
226
  return 1
216
227
  }
217
228
 
229
+ const templateName = flag(args, 'template') ?? terminalBoxTemplate.name
230
+ let template: BoxTemplateResolution
231
+ try {
232
+ template = BOX_TEMPLATES.resolve({ name: templateName })
233
+ } catch (error) {
234
+ console.error(
235
+ ` ${rnx} box create: ${error instanceof Error ? error.message : String(error)}.`,
236
+ )
237
+ return 1
238
+ }
239
+
218
240
  const root = findRepositoryPackageRoot(process.cwd())
219
241
  if (!root) {
220
242
  console.error(
@@ -224,7 +246,7 @@ async function runCloudBoxCreate(
224
246
  return 1
225
247
  }
226
248
 
227
- const plane = new CheckoutFilePlane(root)
249
+ const plane = new CheckoutFilePlane(root, { trackedOnly: true })
228
250
  try {
229
251
  await plane.refresh()
230
252
  } catch (error) {
@@ -242,36 +264,55 @@ async function runCloudBoxCreate(
242
264
  const { header } = authHeaderOrExit('box create')
243
265
  const apiKey = header.replace(/^Bearer /, '')
244
266
 
245
- console.log(` ...creating cloud box ${name}`)
246
- let session: Awaited<ReturnType<typeof connectCloudBox>> | null = null
267
+ if (!json) console.log(` ...creating cloud box ${name}`)
268
+ const client = createBoxClient(
269
+ createCloudBoxProvider({ endpoint, apiOrigin: apiOrigin(args), apiKey }),
270
+ )
271
+ let session: BoxShellSession | null = null
247
272
  let heartbeat: { stop: () => void } | null = null
248
273
  try {
249
- const created = await createCloudBox({
250
- endpoint,
274
+ const box = await client.create({
251
275
  name,
252
- apiKey,
253
- plane,
254
- onProgress: opts.verbose
255
- ? (seeded, total) => console.log(` ...seeded ${seeded}/${total} files`)
256
- : undefined,
276
+ stack: template.stack,
277
+ template: template.provenance,
278
+ source: plane,
279
+ onProgress:
280
+ opts.verbose && !json
281
+ ? (seeded, total) => console.log(` ...seeded ${seeded}/${total} files`)
282
+ : undefined,
257
283
  })
258
- console.log(` ...seeded ${created.files} project files`)
284
+ if (json) {
285
+ console.log(
286
+ JSON.stringify({
287
+ ...box.description,
288
+ ...(box.accessToken ? { accessToken: box.accessToken } : {}),
289
+ }),
290
+ )
291
+ return 0
292
+ }
293
+ console.log(` ...created Box ${box.id}`)
294
+ if (box.description.stableUrl) {
295
+ console.log(` ...live ${box.description.stableUrl}`)
296
+ }
297
+ console.log(` ...seeded ${box.description.files} project files`)
259
298
  // shown once and never stored: this process does not own a credential
260
299
  // store, and inventing one here would be a second place secrets live.
261
- console.log(` ...box token (shown once): ${created.token}`)
300
+ console.log(` ...box token (shown once): ${box.accessToken}`)
262
301
  console.log(` ...accessing shell\n`)
263
- session = await connectCloudBox({ endpoint, name, token: created.token })
302
+ session = await box.connect()
264
303
  // bank the wall clock while the shell is open. the box holds no account
265
304
  // credential, so the client that is using it is what keeps the session
266
305
  // alive; when this process dies the sweep closes it at the last heartbeat.
306
+ const sessionId = box.description.sessionId
307
+ if (!sessionId) throw new BoxClientError('the box service returned no session id')
267
308
  heartbeat = heartbeatSession({
268
309
  apiOrigin: apiOrigin(args),
269
310
  apiKey,
270
- sessionId: created.sessionId,
311
+ sessionId,
271
312
  })
272
313
  return await repl(session, '')
273
314
  } catch (error) {
274
- if (error instanceof CloudBoxError) {
315
+ if (error instanceof BoxClientError) {
275
316
  console.error(` ${rnx} box create: ${error.message}`)
276
317
  return 1
277
318
  }
@@ -311,13 +352,28 @@ async function runLocalBox(opts: BoxCommandOptions): Promise<number> {
311
352
  throw error
312
353
  }
313
354
 
314
- const shell = createHarnessShell({
315
- projectFs: new HarnessProjectFs(plane),
316
- commands: [createRnxCommand({ root, shellCwd: () => shell.cwd })],
317
- // just-bash's JavaScript rg reads every file body through the plane.
318
- // see search-command.ts for the numbers.
319
- builtInCommandOverrides: [createSearchCommand({ root })],
355
+ let session: BoxShellSession | null = null
356
+ const client = createBoxClient(
357
+ createBoxHostProvider({
358
+ shell: {
359
+ commands: [
360
+ createRnxCommand({
361
+ root,
362
+ shellCwd: () => session?.cwd ?? PROJECT_MOUNT,
363
+ }),
364
+ ],
365
+ // just-bash's JavaScript rg reads every file body through the plane.
366
+ // see search-command.ts for the numbers.
367
+ builtInCommandOverrides: [createSearchCommand({ root })],
368
+ },
369
+ }),
370
+ )
371
+ const box = await client.create({
372
+ name: path.basename(root),
373
+ stack: terminalBoxStack,
374
+ source: plane,
320
375
  })
376
+ session = await box.connect()
321
377
 
322
378
  const relative = path.relative(root, process.cwd())
323
379
  console.log(` ...starting local box`)
@@ -362,26 +418,23 @@ async function runLocalBox(opts: BoxCommandOptions): Promise<number> {
362
418
 
363
419
  try {
364
420
  // the checkout is shared with editors, agents, and the user's own terminal.
365
- // HarnessProjectFs arms a stale bit on each shell turn and refreshes the
421
+ // BoxProjectFs arms a stale bit on each shell turn and refreshes the
366
422
  // plane synchronously on the first actual file access, so commands that
367
423
  // never touch project files pay nothing, while reads always see fresh disk state.
368
- return await repl(shell, relative)
424
+ return await repl(session, relative)
369
425
  } finally {
426
+ session.close()
370
427
  shared?.close()
371
428
  }
372
429
  }
373
430
 
374
431
  // the REPL drives whatever holds the shell. locally that is an in-process
375
- // HarnessShell over this checkout; for a cloud box it is a socket to the box,
432
+ // BoxShell over this checkout; for a cloud box it is a socket to the box,
376
433
  // which holds both the files and the shell. same loop, because the only
377
434
  // difference is where exec runs.
378
- interface BoxSession {
379
- readonly cwd: string
380
- exec(command: string): Promise<HarnessShellResult>
381
- }
382
435
 
383
436
  async function repl(
384
- session: BoxSession,
437
+ session: BoxShellSession,
385
438
  startRelative: string,
386
439
  beforeCommand?: () => Promise<void>,
387
440
  ): Promise<number> {
@@ -9,8 +9,9 @@ usage:
9
9
  rnx config telemetry [on|off|status]
10
10
 
11
11
  environment:
12
- RNX_HINTS=off suppress CLI hints
13
- RNX_HINTS=always show every CLI hint every time
12
+ RNX_HINTS=off suppress CLI hints
13
+ RNX_HINTS=always show every CLI hint every time
14
+ RNX_UPDATE_CHECK=off disable background CLI update checks and notices
14
15
  `)
15
16
  }
16
17
 
@@ -339,7 +339,7 @@ function buildShellPathOpenUrl(target: string, baseUrl: string): string {
339
339
  return url.toString()
340
340
  }
341
341
 
342
- // SootSim share/preview/build links (e.g. https://sootsim.com/preview/<id>,
342
+ // rnx share/preview/build links (e.g. https://rnxsim.com/preview/<id>,
343
343
  // https://contrast.dev/build/<owner>/<repo>/<branch>) are full player pages
344
344
  // served by the Contrast app — NOT Metro bundles and NOT driveable shell sims.
345
345
  // passing one to `rnx open` used to fall through to the bundle resolver,
@@ -942,7 +942,7 @@ export function printConnectedSims(sims: BridgeSimInfo[], selectedId?: string) {
942
942
 
943
943
  // classify which engine a sim is running so dev-vs-published is never ambiguous
944
944
  // to agents or humans reading `rnx list`. dev shell serves local source on
945
- // :5173; the Contrast app embeds sootsim on :3000; the bridge/daemon and the
945
+ // :5173; the Contrast app embeds rnx on :3000; the bridge/daemon and the
946
946
  // /__soot plugin serve the active runtime dir — which, inside a dev checkout, is
947
947
  // the fresh dev-stack build (resolveDevCheckoutRuntimeRoot mode boundary), not a
948
948
  // published runtime. keep this label honest so it never says "published" while
@@ -1126,9 +1126,9 @@ async function waitForProcessExit(pid: number, timeoutMs = 2_500): Promise<boole
1126
1126
 
1127
1127
  // a sim record's host pid can outlive its process; after pid reuse the number
1128
1128
  // may point at an unrelated process (real incident 2026-07-14: a stale record
1129
- // made `sootsim close` SIGTERM the shared vite shell dev server, taking the
1129
+ // made `rnx close` SIGTERM the shared vite shell dev server, taking the
1130
1130
  // whole dev stack down). only signal a pid whose argv proves it is still a
1131
- // sootsim playwright host launcher.
1131
+ // rnx playwright host launcher.
1132
1132
  function isPlaywrightHostProcess(pid: number): boolean {
1133
1133
  // `ps eww` prints argv then the process ENVIRONMENT for same-user
1134
1134
  // processes. the marker must live in the env SUFFIX (everything past the
@@ -1530,9 +1530,9 @@ export async function runOpenCommand(
1530
1530
  }
1531
1531
 
1532
1532
  // metro HMR mode. interactive opens default to hot=true so edits hot-apply
1533
- // through sootsim's HmrClient — expo CLI's createBundleUrlSearchParams
1533
+ // through rnx's HmrClient — expo CLI's createBundleUrlSearchParams
1534
1534
  // hardcodes hot=false on every launchAsset URL (a leftover toggle real iOS
1535
- // sims tolerate but sootsim couldn't until now), so without this flip the
1535
+ // sims tolerate but rnx couldn't until now), so without this flip the
1536
1536
  // bundle prelude doesn't include metro's HMR runtime + react-refresh and
1537
1537
  // edits silently no-op. `--driver playwright` (agent / CI runs) defaults
1538
1538
  // to hot=false for deterministic e2e. explicit `--hot` / `--no-hmr` win.
@@ -1,7 +1,7 @@
1
1
  // rnx perf cpu — capture sampled CPU traces from the page and every worker.
2
2
  //
3
3
  // the JS Self-Profiler API (`new Profiler()`) cannot be constructed in a
4
- // dedicated-worker scope, and sootsim runs the guest app in the tenant worker,
4
+ // dedicated-worker scope, and rnx runs the guest app in the tenant worker,
5
5
  // so the old in-worker approach was unsupported (F28). this implementation
6
6
  // attaches Chromium's `Profiler` CDP domain to the page and worker targets —
7
7
  // which returns the .cpuprofile shape directly for every execution context.
@@ -10,7 +10,7 @@
10
10
  // target, `Target.setAutoAttach` to discover the worker child sessions, and
11
11
  // route `Profiler.*` to each worker via its sessionId (flatten mode).
12
12
  //
13
- // the user's normal sootsim tab runs in plain Chrome without a debug port.
13
+ // the user's normal rnx tab runs in plain Chrome without a debug port.
14
14
  // the easiest way to get a profilable sim is to open one with a CDP port —
15
15
  // it stays driveable over the WS bridge, so you can scroll/navigate while
16
16
  // profiling (the playwright driver passes --remote-debugging-port through):
@@ -1,8 +1,8 @@
1
- // rnx daemon — manage the persistent sootsim bridge daemon
1
+ // rnx daemon — manage the persistent rnx bridge daemon
2
2
  //
3
3
  // wraps `rnx serve` so it starts automatically on login and restarts if
4
- // it crashes. the agent is tied to whichever `sootsim` binary resolves at
5
- // registration time, so `npm update -g sootsim` + `rnx daemon restart`
4
+ // it crashes. the agent is tied to whichever `rnxsim` binary resolves at
5
+ // registration time, so `npm update -g rnxsim` + `rnx daemon restart`
6
6
  // picks up the upgrade without touching the plist/unit file.
7
7
  //
8
8
  // `daemonInstall` is shared by `rnx setup` and the explicit
@@ -104,10 +104,10 @@ function resolveSootsimInvocation(): SootsimInvocation {
104
104
  // launchd / systemd run agents with a stripped PATH (basically just
105
105
  // /usr/bin:/bin:/usr/sbin:/sbin), so a `#!/usr/bin/env node` shebang
106
106
  // resolves nothing and the daemon never starts. spawn the absolute node
107
- // binary directly with the sootsim entry script as an argument.
107
+ // binary directly with the rnx entry script as an argument.
108
108
  //
109
109
  // process.execPath is whichever node ran the install command, which is
110
- // also the node the user expects sootsim to run on.
110
+ // also the node the user expects rnx to run on.
111
111
  const npxInvocation = resolveNpxDaemonInvocation()
112
112
  if (npxInvocation) return npxInvocation
113
113
  const entry = resolveSootsimEntryScript()
@@ -433,7 +433,7 @@ export async function daemonInstall({ port, force }: { port: number; force: bool
433
433
 
434
434
  // user-scope systemd units are killed when the user's last session ends.
435
435
  // on most distros the daemon then disappears at logout — the unit is
436
- // "active" until you log off, then silently dead, and `sootsim` looks
436
+ // "active" until you log off, then silently dead, and `rnx` looks
437
437
  // broken when you ssh back in. `loginctl enable-linger` keeps the user
438
438
  // bus alive across sessions, which is what every other long-running
439
439
  // user agent (gpg-agent, ssh-agent --systemd) also relies on.
@@ -513,7 +513,7 @@ export function teardownLegacyMacDaemonArtifacts(
513
513
  * and its support files (mac .app bundle, log dir). does NOT touch user
514
514
  * data under ~/.rnx. idempotent: each entry is checked before remove.
515
515
  * exported for reuse by the npm preuninstall hook (scripts/preuninstall.cjs)
516
- * via the bundled CLI so a `npm rm -g sootsim` tears down the daemon before
516
+ * via the bundled CLI so a `npm rm -g rnxsim` tears down the daemon before
517
517
  * the bin disappears. */
518
518
  export function teardownDaemonService(): TeardownReport {
519
519
  const removed: string[] = []
@@ -1,6 +1,6 @@
1
1
  // rnx debug — drive __sootsimDebug.* from the terminal
2
2
  //
3
- // connects to a running sootsim instance via the WebSocket bridge and forwards
3
+ // connects to a running rnx instance via the WebSocket bridge and forwards
4
4
  // debug channel management, tree snapshots, and on-demand inspectors without
5
5
  // requiring a browser console.
6
6
  //
@@ -78,7 +78,7 @@ subcommands:
78
78
  scroll <id>, scroll-hit <x> <y>,
79
79
  hit <x> <y>, gesture <x> <y>
80
80
 
81
- js <code> execute javascript in the running app. SootSim
81
+ js <code> execute javascript in the running app. rnx
82
82
  is the canonical state object — reach into its
83
83
  bridges / state / chrome / debug slots directly.
84
84
  e.g. SootSim.bridges.test.findByText("Sign in")
@@ -1,10 +1,10 @@
1
- // sootsim detox — run existing detox suites against sootsim
1
+ // rnx detox — run existing detox suites against rnx
2
2
  //
3
3
  // the command is a thin orchestration layer over `npx jest`:
4
4
  // 1. resolves a test dir (cwd e2e|test/e2e|detox, or the flag)
5
- // 2. makes sure a sootsim shell is reachable (auto-launches one if not)
5
+ // 2. makes sure an rnx shell is reachable (auto-launches one if not)
6
6
  // 3. spawns jest with our preset baked in so `import ... from 'detox'`
7
- // resolves to the sootsim driver
7
+ // resolves to the rnx driver
8
8
  //
9
9
  // it deliberately shells out to the user's own jest so their jest version,
10
10
  // transforms, and setupFiles keep working. the only thing we force is the
@@ -129,7 +129,7 @@ export async function runDetox(args: string[], opts: RunDetoxOpts = {}): Promise
129
129
  rnxExit(1)
130
130
  }
131
131
 
132
- // auto-launch: make sure a sootsim shell is reachable before running jest.
132
+ // auto-launch: make sure an rnx shell is reachable before running jest.
133
133
  // Reuse the Maestro discovery path: scan local dev servers.
134
134
  // and `rnx open` the first match.
135
135
  if (!noLaunch) {
@@ -227,7 +227,7 @@ export async function runDetox(args: string[], opts: RunDetoxOpts = {}): Promise
227
227
 
228
228
  // jest owns the browsers, and it only reaps them from its own signal and exit
229
229
  // handlers. this process is the one a caller has a pid for, so a SIGTERM aimed
230
- // at `sootsim detox` lands here and jest never hears about it: it is a
230
+ // at `rnx detox` lands here and jest never hears about it: it is a
231
231
  // grandchild (this -> npx -> jest), so it keeps running with no parent and
232
232
  // every chrome it launched is orphaned for good. forward the signal instead of
233
233
  // exiting out from under it, and let its exit code end this process.
@@ -362,8 +362,8 @@ function initDetoxProject(): void {
362
362
  writeFileSync(
363
363
  configPath,
364
364
  `// rnx detox jest config.
365
- // extends the sootsim preset, which rewrites \`import ... from 'detox'\`
366
- // to the sootsim driver and keeps your jest transforms intact.
365
+ // extends the rnx preset, which rewrites \`import ... from 'detox'\`
366
+ // to the rnx driver and keeps your jest transforms intact.
367
367
 
368
368
  /** @type {import('@jest/types').Config.InitialOptions} */
369
369
  module.exports = {
@@ -32,7 +32,7 @@ import type {
32
32
  SootSimTimelineEvent,
33
33
  SootSimTimelineQuery,
34
34
  SootSimTimelineQueryResult,
35
- } from '@sootsim/globals'
35
+ } from '@rnx/globals'
36
36
 
37
37
  interface DiagnoseOptions {
38
38
  port?: number
@@ -1,4 +1,4 @@
1
- // sootsim desktop — launch the installed desktop companion via the
1
+ // rnx desktop — launch the installed desktop companion via the
2
2
  // electron driver. the driver wraps findDesktopCompanion +
3
3
  // launchDesktopCompanion, so this command is a thin CLI surface that
4
4
  // parses options, prompts for install, and delegates everything else.
@@ -148,7 +148,7 @@ examples:
148
148
  )
149
149
  if (!started) {
150
150
  console.error(
151
- ' film: engine recording store did not start (is @sootsim/plugin-recording installed?)',
151
+ ' film: engine recording store did not start (is @rnx/plugin-recording installed?)',
152
152
  )
153
153
  return 1
154
154
  }
@@ -1,5 +1,5 @@
1
1
  // shared Maestro YAML playback and live-authoring implementation.
2
- // discovers a sootsim target when needed, then reuses the shared bridge action layer
2
+ // discovers an rnx target when needed, then reuses the shared bridge action layer
3
3
 
4
4
  import * as fs from 'fs'
5
5
  import { createHash, randomUUID } from 'node:crypto'
@@ -82,7 +82,7 @@ export async function resolveAppFrontmatterTarget(
82
82
  return { target: trimmed }
83
83
  }
84
84
 
85
- // discover a sootsim shell URL — prefer patched servers, fall back to any match.
85
+ // discover an rnx shell URL — prefer patched servers, fall back to any match.
86
86
  // exported so `detox.ts` and `maestro.ts` can reuse the same discovery logic.
87
87
  export async function discoverSootsimUrl(): Promise<string | null> {
88
88
  const servers = await scanDevServers()
@@ -143,7 +143,7 @@ options:
143
143
  --preview-origin <url>
144
144
  upload target for --preview (default: auto)
145
145
  --preview-public-origin <url>
146
- public link origin for --preview (default: sootsim.com
146
+ public link origin for --preview (default: rnxsim.com
147
147
  for prod uploads)
148
148
  --billing-kind test_run
149
149
  meter the chained preview upload as a test run
@@ -371,7 +371,7 @@ flow extension:
371
371
  ) {
372
372
  const discovered = await discoverSootsimUrl()
373
373
  if (!discovered) {
374
- console.error(' error: no current sim and no sootsim target found')
374
+ console.error(' error: no current sim and no rnx target found')
375
375
  rnxExit(1)
376
376
  }
377
377
  openTarget = discovered