rnxsim 0.1.455 → 0.1.456

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 (58) hide show
  1. package/cli/cloud-client.ts +3 -1
  2. package/cli/cloud-dispatch.ts +17 -0
  3. package/cli/commands/do-chain.ts +1 -4
  4. package/dist-lib/agent-daemon-client.cjs +1 -1
  5. package/dist-lib/agent-events.cjs +1 -1
  6. package/dist-lib/agent-identity.cjs +1 -1
  7. package/dist-lib/agent-sessions.cjs +1 -1
  8. package/dist-lib/attached-projects.cjs +1 -1
  9. package/dist-lib/auth/shared-session.cjs +1 -1
  10. package/dist-lib/backend-origin.cjs +1 -1
  11. package/dist-lib/beta.cjs +1 -1
  12. package/dist-lib/beta.mjs +1 -1
  13. package/dist-lib/bridge-constants.cjs +1 -1
  14. package/dist-lib/bridge-contract-input.cjs +1 -1
  15. package/dist-lib/bridge-contract-input.mjs +1 -1
  16. package/dist-lib/bridge-contract.cjs +1 -1
  17. package/dist-lib/bridge-contract.mjs +1 -1
  18. package/dist-lib/capture-contract.cjs +1 -1
  19. package/dist-lib/capture-contract.mjs +1 -1
  20. package/dist-lib/cli-constants.cjs +1 -1
  21. package/dist-lib/cloud-contract.cjs +1 -1
  22. package/dist-lib/cloud-contract.mjs +1 -1
  23. package/dist-lib/cloud.cjs +1 -1
  24. package/dist-lib/cloud.mjs +1 -1
  25. package/dist-lib/config.cjs +1 -1
  26. package/dist-lib/detox/index.cjs +1 -1
  27. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  28. package/dist-lib/home-paths.cjs +1 -1
  29. package/dist-lib/host/bridge-host.cjs +1 -1
  30. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  31. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  32. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  33. package/dist-lib/host/replacement-module-handler.cjs +1 -1
  34. package/dist-lib/host/websocket-proxy.cjs +1 -1
  35. package/dist-lib/index.cjs +541 -227
  36. package/dist-lib/jump-to-source-babel.cjs +1 -1
  37. package/dist-lib/jump-to-source-native.cjs +1 -1
  38. package/dist-lib/menu.cjs +1 -1
  39. package/dist-lib/menu.mjs +1 -1
  40. package/dist-lib/metro-fingerprint-registry.cjs +1 -1
  41. package/dist-lib/metro-fingerprint-registry.mjs +1 -1
  42. package/dist-lib/metro-production-bundle.cjs +1 -1
  43. package/dist-lib/metro-production-bundle.mjs +1 -1
  44. package/dist-lib/metro.cjs +1 -1
  45. package/dist-lib/profiles.cjs +1 -1
  46. package/dist-lib/public-brand.cjs +1 -1
  47. package/dist-lib/react-native-host-modules.cjs +1 -1
  48. package/dist-lib/react-native-host-modules.mjs +1 -1
  49. package/dist-lib/render-mode.cjs +1 -1
  50. package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
  51. package/dist-lib/sdk.cjs +1 -1
  52. package/dist-lib/sdk.mjs +1 -1
  53. package/dist-lib/skills.cjs +368 -39
  54. package/dist-lib/vite.cjs +1 -1
  55. package/package.json +1 -1
  56. package/skills/rnx-debug/SKILL.md +1 -1
  57. package/src/bridge-contract.ts +2 -1
  58. package/src/connect.ts +18 -0
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.455 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.456 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;
@@ -56,6 +56,514 @@ __export(src_exports, {
56
56
  });
57
57
  module.exports = __toCommonJS(src_exports);
58
58
 
59
+ // cli/cloud-client.ts
60
+ var import_node_crypto = require("node:crypto");
61
+
62
+ // ../sootsim-engine/src/metro-fingerprint-registry-client.ts
63
+ var import_metro_fingerprint_registry = require("rnxsim/metro-fingerprint-registry");
64
+
65
+ // ../sootsim-engine/src/metro-fingerprint.ts
66
+ var import_metro_production_bundle = require("rnxsim/metro-production-bundle");
67
+ var import_react_native_host_modules = require("rnxsim/react-native-host-modules");
68
+
69
+ // src/backend-origin.ts
70
+ function normalizeHostname(hostname) {
71
+ return hostname.replace(/^\[|\]$/g, "").toLowerCase();
72
+ }
73
+ function isLoopbackHost(hostname) {
74
+ const normalized = normalizeHostname(hostname);
75
+ return normalized === "localhost" || normalized.endsWith(".localhost") || normalized === "0.0.0.0" || normalized === "::1" || /^127(?:\.\d{1,3}){3}$/.test(normalized);
76
+ }
77
+
78
+ // src/cloud-contract.ts
79
+ var RNX_CLOUD_MAX_ARTIFACT_BYTES = 20 * 1024 * 1024;
80
+ var RNX_CLOUD_DESIGN_MAX_STORAGE_BYTES = 1024 * 1024;
81
+ var RNX_CLOUD_COMMAND_TYPES = Object.freeze([
82
+ "state",
83
+ "query",
84
+ "resolve",
85
+ "perform",
86
+ "waitFor",
87
+ "reset",
88
+ "screenshot",
89
+ "capture",
90
+ "captureRegions",
91
+ "settle",
92
+ "setAppearance",
93
+ "openUrl",
94
+ "tree",
95
+ "tap",
96
+ "longPress",
97
+ "memory",
98
+ "diagnostics"
99
+ ]);
100
+ var RNX_CLOUD_COMMAND_TYPE_SET = new Set(RNX_CLOUD_COMMAND_TYPES);
101
+ function isRnxCloudCommandType(value) {
102
+ return RNX_CLOUD_COMMAND_TYPE_SET.has(value);
103
+ }
104
+
105
+ // src/metro-production-bundle.ts
106
+ var RNXSIM_METRO_MODULE_PATHS_PREFIX = "globalThis.__sootsimModulePaths=";
107
+ var RNX_METRO_MODULE_IDENTITY_VERSION = 2;
108
+ var RNXSIM_METRO_SOURCE_MAP_COMMENT = "//# sourceMappingURL=";
109
+ function detachRNXMetroSourceMapUrl(bundleText) {
110
+ const trimmedEnd = bundleText.replace(/\s+$/, "");
111
+ const commentStart = trimmedEnd.lastIndexOf(`
112
+ ${RNXSIM_METRO_SOURCE_MAP_COMMENT}`);
113
+ if (commentStart < 0) return { source: bundleText, sourceMappingUrl: null };
114
+ const url = trimmedEnd.slice(commentStart + 1 + RNXSIM_METRO_SOURCE_MAP_COMMENT.length);
115
+ if (url.includes("\n")) return { source: bundleText, sourceMappingUrl: null };
116
+ return { source: trimmedEnd.slice(0, commentStart), sourceMappingUrl: url };
117
+ }
118
+ function createRNXMetroModuleIdentityFooter(identity) {
119
+ const payload = JSON.stringify(identity).replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029").replace(/<\//g, "<\\/");
120
+ return `
121
+ ${RNXSIM_METRO_MODULE_PATHS_PREFIX}${payload};`;
122
+ }
123
+ function appendRNXMetroModuleIdentityFooter(bundleText, identity) {
124
+ const footer = createRNXMetroModuleIdentityFooter(identity);
125
+ const detached = detachRNXMetroSourceMapUrl(bundleText);
126
+ return detached.sourceMappingUrl === null ? bundleText + footer : `${detached.source}${footer}
127
+ ${RNXSIM_METRO_SOURCE_MAP_COMMENT}${detached.sourceMappingUrl}`;
128
+ }
129
+
130
+ // src/public-brand.ts
131
+ var name = "rnx";
132
+ var rnxPublicBrand = Object.freeze({
133
+ name,
134
+ packageName: "rnxsim",
135
+ commandName: "rnx",
136
+ domain: "rnxsim.com",
137
+ origin: "https://rnxsim.com"
138
+ });
139
+
140
+ // src/home-paths.ts
141
+ var import_node_fs = __toESM(require("node:fs"), 1);
142
+ var import_node_os = require("node:os");
143
+ var import_node_path = __toESM(require("node:path"), 1);
144
+ var RNX_HOME_ENV = "RNX_HOME";
145
+ var ACTIVE_RUNTIME_FILE = "active";
146
+ var DAEMON_LOCKFILE = "daemon.json";
147
+ var DEV_BRIDGES_DIR = "dev-bridges";
148
+ var DAEMON_HEARTBEAT_STALE_MS = 3e4;
149
+ function rnxHomeDir() {
150
+ const override = process.env[RNX_HOME_ENV];
151
+ if (override && override.length > 0) return import_node_path.default.resolve(override);
152
+ return import_node_path.default.join((0, import_node_os.homedir)(), ".rnx");
153
+ }
154
+ function runtimesDir() {
155
+ return import_node_path.default.join(rnxHomeDir(), "runtimes");
156
+ }
157
+ function runtimeDir(version) {
158
+ return import_node_path.default.join(runtimesDir(), version);
159
+ }
160
+ function activeRuntimeFile() {
161
+ return import_node_path.default.join(runtimesDir(), ACTIVE_RUNTIME_FILE);
162
+ }
163
+ function daemonLockfilePath() {
164
+ return import_node_path.default.join(rnxHomeDir(), DAEMON_LOCKFILE);
165
+ }
166
+ function devBridgesDir() {
167
+ return import_node_path.default.join(rnxHomeDir(), DEV_BRIDGES_DIR);
168
+ }
169
+ function readActiveRuntime() {
170
+ try {
171
+ const value = import_node_fs.default.readFileSync(activeRuntimeFile(), "utf8").trim();
172
+ return value.length > 0 ? value : null;
173
+ } catch {
174
+ return null;
175
+ }
176
+ }
177
+ var DAEMON_LOCKFILE_MAX_BYTES = 16 * 1024;
178
+ function readDaemonLockfile() {
179
+ try {
180
+ const fd = import_node_fs.default.openSync(daemonLockfilePath(), "r");
181
+ try {
182
+ const buf = Buffer.alloc(DAEMON_LOCKFILE_MAX_BYTES);
183
+ const bytesRead = import_node_fs.default.readSync(fd, buf, 0, DAEMON_LOCKFILE_MAX_BYTES, 0);
184
+ const raw = buf.subarray(0, bytesRead).toString("utf8");
185
+ const parsed = JSON.parse(raw);
186
+ if (parsed && parsed.schema === 1 && typeof parsed.pid === "number" && typeof parsed.bridgePort === "number" && typeof parsed.runtimePort === "number" && typeof parsed.startedAt === "number" && typeof parsed.heartbeatAt === "number" && (parsed.servedRuntimes === void 0 || Array.isArray(parsed.servedRuntimes) && parsed.servedRuntimes.every((version) => typeof version === "string"))) {
187
+ return parsed;
188
+ }
189
+ return null;
190
+ } finally {
191
+ import_node_fs.default.closeSync(fd);
192
+ }
193
+ } catch {
194
+ return null;
195
+ }
196
+ }
197
+ function parseDevBridgeLockfile(raw) {
198
+ try {
199
+ const parsed = JSON.parse(raw);
200
+ if (parsed && parsed.schema === 1 && parsed.source === "vite-dev" && typeof parsed.pid === "number" && typeof parsed.bridgePort === "number" && typeof parsed.runtimePort === "number" && typeof parsed.cwd === "string" && typeof parsed.startedAt === "number" && typeof parsed.heartbeatAt === "number" && (parsed.shellPort === void 0 || typeof parsed.shellPort === "number") && (parsed.servedRuntimes === void 0 || Array.isArray(parsed.servedRuntimes) && parsed.servedRuntimes.every((version) => typeof version === "string"))) {
201
+ return parsed;
202
+ }
203
+ return null;
204
+ } catch {
205
+ return null;
206
+ }
207
+ }
208
+ function readDevBridgeLockfiles() {
209
+ let entries;
210
+ try {
211
+ entries = import_node_fs.default.readdirSync(devBridgesDir(), { withFileTypes: true });
212
+ } catch {
213
+ return [];
214
+ }
215
+ const lockfiles = [];
216
+ for (const entry of entries) {
217
+ if (!entry.isFile() || !/^\d+\.json$/.test(entry.name)) continue;
218
+ try {
219
+ const filePath = import_node_path.default.join(devBridgesDir(), entry.name);
220
+ const fd = import_node_fs.default.openSync(filePath, "r");
221
+ try {
222
+ const buf = Buffer.alloc(DAEMON_LOCKFILE_MAX_BYTES);
223
+ const bytesRead = import_node_fs.default.readSync(fd, buf, 0, DAEMON_LOCKFILE_MAX_BYTES, 0);
224
+ const lockfile = parseDevBridgeLockfile(
225
+ buf.subarray(0, bytesRead).toString("utf8")
226
+ );
227
+ if (lockfile && entry.name === `${lockfile.bridgePort}.json`) {
228
+ lockfiles.push(lockfile);
229
+ }
230
+ } finally {
231
+ import_node_fs.default.closeSync(fd);
232
+ }
233
+ } catch {
234
+ }
235
+ }
236
+ return lockfiles.sort((left, right) => left.bridgePort - right.bridgePort);
237
+ }
238
+ function isDaemonLockfileFresh(lock, now = Date.now()) {
239
+ if (!lock) return false;
240
+ if (now - lock.heartbeatAt > DAEMON_HEARTBEAT_STALE_MS) return false;
241
+ try {
242
+ process.kill(lock.pid, 0);
243
+ return true;
244
+ } catch {
245
+ return false;
246
+ }
247
+ }
248
+ function isDevBridgeLockfileFresh(lock, now = Date.now()) {
249
+ if (!lock) return false;
250
+ if (now - lock.heartbeatAt > DAEMON_HEARTBEAT_STALE_MS) return false;
251
+ try {
252
+ process.kill(lock.pid, 0);
253
+ return true;
254
+ } catch {
255
+ return false;
256
+ }
257
+ }
258
+
259
+ // cli/run-rnx.ts
260
+ var import_node_async_hooks = require("node:async_hooks");
261
+ var sinks = new import_node_async_hooks.AsyncLocalStorage();
262
+
263
+ // cli/auth.ts
264
+ var CHOOSE_ACCOUNT_HINT = "this sign-in belongs to several accounts; run `rnx login --account <name>` to choose the one to bill";
265
+
266
+ // cli/cloud-session.ts
267
+ var CLOUD_SESSION_VERSION = 3;
268
+ var MAX_ENCODED_SESSION_BYTES = 32 * 1024;
269
+ var RNX_CLOUD_SESSION_ENV = "RNX_CLOUD_SESSION";
270
+ var RNX_SHELL_UPDATE_PREFIX = "__RNX_SHELL_UPDATE_V1__";
271
+ var RNX_SHELL_UPDATE_SET_PREFIX = `${RNX_SHELL_UPDATE_PREFIX}set:`;
272
+ var RNX_SHELL_UPDATE_UNSET = `${RNX_SHELL_UPDATE_PREFIX}unset`;
273
+ function isRecord(value) {
274
+ return value !== null && typeof value === "object" && !Array.isArray(value);
275
+ }
276
+ function normalizeCloudOrigin(source) {
277
+ let parsed;
278
+ try {
279
+ parsed = new URL(source);
280
+ } catch {
281
+ throw new Error(`invalid remote simulator origin: ${source}`);
282
+ }
283
+ if (parsed.protocol !== "https:" && parsed.protocol !== "http:" || parsed.username || parsed.password || parsed.pathname !== "/" || parsed.search || parsed.hash) {
284
+ throw new Error(`remote simulator origin must be an HTTP(S) origin: ${source}`);
285
+ }
286
+ if (parsed.protocol === "http:" && !isLoopbackHost(parsed.hostname)) {
287
+ throw new Error(`remote simulator origin requires HTTPS outside loopback: ${source}`);
288
+ }
289
+ return parsed.origin;
290
+ }
291
+ function createCloudSession(input) {
292
+ const session = {
293
+ version: CLOUD_SESSION_VERSION,
294
+ service: "sim",
295
+ boxId: input.boxId.trim(),
296
+ boxToken: input.boxToken,
297
+ simId: input.simId.trim(),
298
+ apiOrigin: normalizeCloudOrigin(input.apiOrigin.trim()),
299
+ claimId: input.claimId.trim(),
300
+ token: input.token
301
+ };
302
+ if (!session.boxId || !session.boxToken || !session.simId || !session.claimId || !session.token) {
303
+ throw new Error("cloud session requires box, simulator, origin, claim, and tokens");
304
+ }
305
+ return session;
306
+ }
307
+ function createCloudBoxSession(input) {
308
+ const session = {
309
+ version: CLOUD_SESSION_VERSION,
310
+ service: "box",
311
+ boxId: input.boxId.trim(),
312
+ boxToken: input.boxToken,
313
+ apiOrigin: normalizeCloudOrigin(input.apiOrigin.trim())
314
+ };
315
+ if (!session.boxId || !session.boxToken) {
316
+ throw new Error("cloud box session requires a box, an origin, and a box token");
317
+ }
318
+ return session;
319
+ }
320
+ function parseCloudSession(encoded) {
321
+ const value = encoded.trim();
322
+ if (!value || value.length > MAX_ENCODED_SESSION_BYTES || !/^[A-Za-z0-9_-]+$/.test(value)) {
323
+ throw new Error(`${RNX_CLOUD_SESSION_ENV} is invalid`);
324
+ }
325
+ let parsed;
326
+ try {
327
+ const bytes = Buffer.from(value, "base64url");
328
+ if (bytes.toString("base64url") !== value) {
329
+ throw new Error("non-canonical cloud session");
330
+ }
331
+ parsed = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
332
+ } catch {
333
+ throw new Error(`${RNX_CLOUD_SESSION_ENV} is invalid`);
334
+ }
335
+ if (!isRecord(parsed) || parsed.version !== CLOUD_SESSION_VERSION) {
336
+ throw new Error(`${RNX_CLOUD_SESSION_ENV} is invalid`);
337
+ }
338
+ const boxId = parsed.boxId;
339
+ const boxToken = parsed.boxToken;
340
+ const apiOrigin = parsed.apiOrigin;
341
+ if (typeof boxId !== "string" || typeof boxToken !== "string" || typeof apiOrigin !== "string") {
342
+ throw new Error(`${RNX_CLOUD_SESSION_ENV} is invalid`);
343
+ }
344
+ try {
345
+ if (parsed.service === "box") {
346
+ return createCloudBoxSession({ boxId, boxToken, apiOrigin });
347
+ }
348
+ const simId = parsed.simId;
349
+ const claimId = parsed.claimId;
350
+ const token = parsed.token;
351
+ if (parsed.service !== "sim" || typeof simId !== "string" || typeof claimId !== "string" || typeof token !== "string") {
352
+ throw new Error("unreadable cloud session");
353
+ }
354
+ return createCloudSession({ boxId, boxToken, simId, apiOrigin, claimId, token });
355
+ } catch {
356
+ throw new Error(`${RNX_CLOUD_SESSION_ENV} is invalid`);
357
+ }
358
+ }
359
+ function readCloudSession(environment = process.env) {
360
+ const encoded = environment[RNX_CLOUD_SESSION_ENV];
361
+ return encoded?.trim() ? parseCloudSession(encoded) : null;
362
+ }
363
+
364
+ // cli/cloud-client.ts
365
+ var DEFAULT_RNX_CLOUD_ORIGIN = rnxPublicBrand.origin;
366
+ function isRecord2(value) {
367
+ return value !== null && typeof value === "object" && !Array.isArray(value);
368
+ }
369
+ function failureSentence(text) {
370
+ let parsed;
371
+ try {
372
+ parsed = JSON.parse(text);
373
+ } catch {
374
+ return null;
375
+ }
376
+ if (!isRecord2(parsed)) return null;
377
+ if (isRecord2(parsed.error) && typeof parsed.error.message === "string") {
378
+ return parsed.error.message;
379
+ }
380
+ if (typeof parsed.message === "string") return parsed.message;
381
+ if (typeof parsed.error === "string") return parsed.error;
382
+ return null;
383
+ }
384
+ var ACCOUNT_CHOICE_REFUSALS = /* @__PURE__ */ new Set([
385
+ "accountId is required",
386
+ "you do not have access to this account"
387
+ ]);
388
+ function responseError(status, text) {
389
+ const parsed = text ? failureSentence(text) : null;
390
+ const sentence = parsed && ACCOUNT_CHOICE_REFUSALS.has(parsed) ? CHOOSE_ACCOUNT_HINT : parsed;
391
+ return new Error(
392
+ `remote simulator request failed (${status})${sentence ? `: ${sentence}` : text.trim() ? `: ${text.trim()}` : ""}`
393
+ );
394
+ }
395
+ function resolveCloudSessionForParsed(parsed) {
396
+ const session = readCloudSession();
397
+ if (!session) return null;
398
+ if (session.service === "box") {
399
+ if (parsed.simIdSource === "flag" && parsed.simId !== session.boxId) {
400
+ throw new Error(
401
+ `this shell is connected to box ${session.boxId}; ${parsed.simId ? `--sim ${parsed.simId}` : "the requested simulator"} is not available in this shell`
402
+ );
403
+ }
404
+ return session;
405
+ }
406
+ if (parsed.simIdSource === "flag" && parsed.simId !== session.simId) {
407
+ throw new Error(
408
+ `this shell is connected to remote simulator ${session.simId}; ${parsed.simId ? `--sim ${parsed.simId}` : "the requested simulator"} is not available in this shell`
409
+ );
410
+ }
411
+ return session;
412
+ }
413
+ function cloudCommand(command) {
414
+ const clean = {};
415
+ for (const [key, value] of Object.entries(command)) {
416
+ if (key !== "id" && key !== "simId") clean[key] = value;
417
+ }
418
+ if (isRnxCloudCommandType(command.type)) return clean;
419
+ throw new Error(
420
+ `a remote simulator does not support bridge command type ${command.type}`
421
+ );
422
+ }
423
+ var CloudBridge = class {
424
+ constructor(session) {
425
+ this.session = session;
426
+ }
427
+ plane = "cloud";
428
+ async send(command, options) {
429
+ const body = {
430
+ id: (0, import_node_crypto.randomUUID)(),
431
+ claimId: this.session.claimId,
432
+ command: cloudCommand(command)
433
+ };
434
+ let response;
435
+ try {
436
+ response = await fetch(
437
+ `${this.session.apiOrigin}/v1/sims/${encodeURIComponent(this.session.simId)}/commands`,
438
+ {
439
+ method: "POST",
440
+ headers: {
441
+ authorization: `Bearer ${this.session.token}`,
442
+ "content-type": "application/json"
443
+ },
444
+ body: JSON.stringify(body),
445
+ ...options?.timeoutMs ? { signal: AbortSignal.timeout(options.timeoutMs) } : {}
446
+ }
447
+ );
448
+ } catch (error) {
449
+ throw new Error(
450
+ `could not reach the remote simulator at ${this.session.apiOrigin}: ${error instanceof Error ? error.message : String(error)}`
451
+ );
452
+ }
453
+ const text = await response.text();
454
+ if (!response.ok) throw responseError(response.status, text);
455
+ let parsed;
456
+ try {
457
+ parsed = JSON.parse(text);
458
+ } catch {
459
+ throw new Error("remote simulator command returned unreadable JSON");
460
+ }
461
+ if (!isRecord2(parsed) || parsed.id !== body.id) {
462
+ throw new Error("remote simulator command returned an invalid command receipt");
463
+ }
464
+ if (typeof parsed.error === "string" && parsed.error) throw new Error(parsed.error);
465
+ if (!Object.hasOwn(parsed, "result")) {
466
+ throw new Error("remote simulator command receipt has no result");
467
+ }
468
+ return parsed.result;
469
+ }
470
+ listSims() {
471
+ return Promise.reject(
472
+ new Error("a remote simulator does not expose a local simulator list")
473
+ );
474
+ }
475
+ focusSim() {
476
+ return Promise.reject(new Error("remote simulators have no browser window to focus"));
477
+ }
478
+ resolveReloadedSim() {
479
+ return Promise.resolve(null);
480
+ }
481
+ closeSim() {
482
+ return Promise.reject(
483
+ new Error("remote simulator close uses the simulator DELETE route")
484
+ );
485
+ }
486
+ claim() {
487
+ return Promise.reject(
488
+ new Error("remote simulator claim uses the simulator claim route")
489
+ );
490
+ }
491
+ close() {
492
+ }
493
+ };
494
+ var CloudBoxBridge = class {
495
+ constructor(session) {
496
+ this.session = session;
497
+ }
498
+ plane = "cloud";
499
+ async send(command, options) {
500
+ const id = (0, import_node_crypto.randomUUID)();
501
+ const payload = {};
502
+ for (const [key, value] of Object.entries(command)) {
503
+ if (key !== "id" && key !== "simId") payload[key] = value;
504
+ }
505
+ let response;
506
+ try {
507
+ response = await fetch(
508
+ `${this.session.apiOrigin}/v1/boxes/${encodeURIComponent(this.session.boxId)}/commands`,
509
+ {
510
+ method: "POST",
511
+ headers: {
512
+ authorization: `Bearer ${this.session.boxToken}`,
513
+ "content-type": "application/json"
514
+ },
515
+ body: JSON.stringify({ id, command: payload }),
516
+ ...options?.timeoutMs ? { signal: AbortSignal.timeout(options.timeoutMs) } : {}
517
+ }
518
+ );
519
+ } catch (error) {
520
+ throw new Error(
521
+ `could not reach box ${this.session.boxId} at ${this.session.apiOrigin}: ${error instanceof Error ? error.message : String(error)}`
522
+ );
523
+ }
524
+ const text = await response.text();
525
+ if (!response.ok) throw responseError(response.status, text);
526
+ let parsed;
527
+ try {
528
+ parsed = JSON.parse(text);
529
+ } catch {
530
+ throw new Error("box command returned unreadable JSON");
531
+ }
532
+ if (!isRecord2(parsed) || parsed.id !== id) {
533
+ throw new Error("box command returned an invalid command receipt");
534
+ }
535
+ if (typeof parsed.error === "string" && parsed.error) throw new Error(parsed.error);
536
+ if (!Object.hasOwn(parsed, "result")) {
537
+ throw new Error("box command receipt has no result");
538
+ }
539
+ return parsed.result;
540
+ }
541
+ listSims() {
542
+ return Promise.reject(new Error("a box does not expose a local simulator list"));
543
+ }
544
+ focusSim() {
545
+ return Promise.reject(new Error("a box simulator has no browser window to focus"));
546
+ }
547
+ resolveReloadedSim() {
548
+ return Promise.resolve(null);
549
+ }
550
+ closeSim() {
551
+ return Promise.reject(new Error("close a box simulator with rnx box stop"));
552
+ }
553
+ claim() {
554
+ return Promise.reject(
555
+ new Error("a box simulator is held by its Box page and has nothing to claim")
556
+ );
557
+ }
558
+ close() {
559
+ }
560
+ };
561
+ function createCloudBridgeForParsed(parsed) {
562
+ const session = resolveCloudSessionForParsed(parsed);
563
+ if (!session) return null;
564
+ return session.service === "box" ? new CloudBoxBridge(session) : new CloudBridge(session);
565
+ }
566
+
59
567
  // cli/current-sim.ts
60
568
  var import_fs2 = require("fs");
61
569
  var import_os = require("os");
@@ -215,25 +723,25 @@ function tryBuildDarwinCommFfi() {
215
723
  }
216
724
 
217
725
  // cli/secure-file.ts
218
- var import_node_crypto = require("node:crypto");
219
- var import_node_fs = require("node:fs");
220
- var import_node_path = require("node:path");
726
+ var import_node_crypto2 = require("node:crypto");
727
+ var import_node_fs2 = require("node:fs");
728
+ var import_node_path2 = require("node:path");
221
729
  function writeSecureJsonAtomic(filepath, value) {
222
- (0, import_node_fs.mkdirSync)((0, import_node_path.dirname)(filepath), { recursive: true });
223
- const temporary = `${filepath}.tmp-${process.pid}-${(0, import_node_crypto.randomUUID)()}`;
730
+ (0, import_node_fs2.mkdirSync)((0, import_node_path2.dirname)(filepath), { recursive: true });
731
+ const temporary = `${filepath}.tmp-${process.pid}-${(0, import_node_crypto2.randomUUID)()}`;
224
732
  let descriptor = null;
225
733
  try {
226
- descriptor = (0, import_node_fs.openSync)(temporary, "wx", 384);
227
- (0, import_node_fs.fchmodSync)(descriptor, 384);
228
- (0, import_node_fs.writeFileSync)(descriptor, `${JSON.stringify(value, null, 2)}
734
+ descriptor = (0, import_node_fs2.openSync)(temporary, "wx", 384);
735
+ (0, import_node_fs2.fchmodSync)(descriptor, 384);
736
+ (0, import_node_fs2.writeFileSync)(descriptor, `${JSON.stringify(value, null, 2)}
229
737
  `);
230
- (0, import_node_fs.fsyncSync)(descriptor);
231
- (0, import_node_fs.closeSync)(descriptor);
738
+ (0, import_node_fs2.fsyncSync)(descriptor);
739
+ (0, import_node_fs2.closeSync)(descriptor);
232
740
  descriptor = null;
233
- (0, import_node_fs.renameSync)(temporary, filepath);
741
+ (0, import_node_fs2.renameSync)(temporary, filepath);
234
742
  } catch (error) {
235
- if (descriptor !== null) (0, import_node_fs.closeSync)(descriptor);
236
- (0, import_node_fs.rmSync)(temporary, { force: true });
743
+ if (descriptor !== null) (0, import_node_fs2.closeSync)(descriptor);
744
+ (0, import_node_fs2.rmSync)(temporary, { force: true });
237
745
  throw error;
238
746
  }
239
747
  }
@@ -394,213 +902,6 @@ function resolveSootsimBridgePort(input = {}) {
394
902
  // src/cli-constants.ts
395
903
  var DEFAULT_SOOTSIM_SHELL_URL = "http://localhost:5173/";
396
904
 
397
- // src/home-paths.ts
398
- var import_node_fs2 = __toESM(require("node:fs"), 1);
399
- var import_node_os = require("node:os");
400
- var import_node_path2 = __toESM(require("node:path"), 1);
401
- var RNX_HOME_ENV = "RNX_HOME";
402
- var ACTIVE_RUNTIME_FILE = "active";
403
- var DAEMON_LOCKFILE = "daemon.json";
404
- var DEV_BRIDGES_DIR = "dev-bridges";
405
- var DAEMON_HEARTBEAT_STALE_MS = 3e4;
406
- function rnxHomeDir() {
407
- const override = process.env[RNX_HOME_ENV];
408
- if (override && override.length > 0) return import_node_path2.default.resolve(override);
409
- return import_node_path2.default.join((0, import_node_os.homedir)(), ".rnx");
410
- }
411
- function runtimesDir() {
412
- return import_node_path2.default.join(rnxHomeDir(), "runtimes");
413
- }
414
- function runtimeDir(version) {
415
- return import_node_path2.default.join(runtimesDir(), version);
416
- }
417
- function activeRuntimeFile() {
418
- return import_node_path2.default.join(runtimesDir(), ACTIVE_RUNTIME_FILE);
419
- }
420
- function daemonLockfilePath() {
421
- return import_node_path2.default.join(rnxHomeDir(), DAEMON_LOCKFILE);
422
- }
423
- function devBridgesDir() {
424
- return import_node_path2.default.join(rnxHomeDir(), DEV_BRIDGES_DIR);
425
- }
426
- function readActiveRuntime() {
427
- try {
428
- const value = import_node_fs2.default.readFileSync(activeRuntimeFile(), "utf8").trim();
429
- return value.length > 0 ? value : null;
430
- } catch {
431
- return null;
432
- }
433
- }
434
- var DAEMON_LOCKFILE_MAX_BYTES = 16 * 1024;
435
- function readDaemonLockfile() {
436
- try {
437
- const fd = import_node_fs2.default.openSync(daemonLockfilePath(), "r");
438
- try {
439
- const buf = Buffer.alloc(DAEMON_LOCKFILE_MAX_BYTES);
440
- const bytesRead = import_node_fs2.default.readSync(fd, buf, 0, DAEMON_LOCKFILE_MAX_BYTES, 0);
441
- const raw = buf.subarray(0, bytesRead).toString("utf8");
442
- const parsed = JSON.parse(raw);
443
- if (parsed && parsed.schema === 1 && typeof parsed.pid === "number" && typeof parsed.bridgePort === "number" && typeof parsed.runtimePort === "number" && typeof parsed.startedAt === "number" && typeof parsed.heartbeatAt === "number" && (parsed.servedRuntimes === void 0 || Array.isArray(parsed.servedRuntimes) && parsed.servedRuntimes.every((version) => typeof version === "string"))) {
444
- return parsed;
445
- }
446
- return null;
447
- } finally {
448
- import_node_fs2.default.closeSync(fd);
449
- }
450
- } catch {
451
- return null;
452
- }
453
- }
454
- function parseDevBridgeLockfile(raw) {
455
- try {
456
- const parsed = JSON.parse(raw);
457
- if (parsed && parsed.schema === 1 && parsed.source === "vite-dev" && typeof parsed.pid === "number" && typeof parsed.bridgePort === "number" && typeof parsed.runtimePort === "number" && typeof parsed.cwd === "string" && typeof parsed.startedAt === "number" && typeof parsed.heartbeatAt === "number" && (parsed.shellPort === void 0 || typeof parsed.shellPort === "number") && (parsed.servedRuntimes === void 0 || Array.isArray(parsed.servedRuntimes) && parsed.servedRuntimes.every((version) => typeof version === "string"))) {
458
- return parsed;
459
- }
460
- return null;
461
- } catch {
462
- return null;
463
- }
464
- }
465
- function readDevBridgeLockfiles() {
466
- let entries;
467
- try {
468
- entries = import_node_fs2.default.readdirSync(devBridgesDir(), { withFileTypes: true });
469
- } catch {
470
- return [];
471
- }
472
- const lockfiles = [];
473
- for (const entry of entries) {
474
- if (!entry.isFile() || !/^\d+\.json$/.test(entry.name)) continue;
475
- try {
476
- const filePath = import_node_path2.default.join(devBridgesDir(), entry.name);
477
- const fd = import_node_fs2.default.openSync(filePath, "r");
478
- try {
479
- const buf = Buffer.alloc(DAEMON_LOCKFILE_MAX_BYTES);
480
- const bytesRead = import_node_fs2.default.readSync(fd, buf, 0, DAEMON_LOCKFILE_MAX_BYTES, 0);
481
- const lockfile = parseDevBridgeLockfile(
482
- buf.subarray(0, bytesRead).toString("utf8")
483
- );
484
- if (lockfile && entry.name === `${lockfile.bridgePort}.json`) {
485
- lockfiles.push(lockfile);
486
- }
487
- } finally {
488
- import_node_fs2.default.closeSync(fd);
489
- }
490
- } catch {
491
- }
492
- }
493
- return lockfiles.sort((left, right) => left.bridgePort - right.bridgePort);
494
- }
495
- function isDaemonLockfileFresh(lock, now = Date.now()) {
496
- if (!lock) return false;
497
- if (now - lock.heartbeatAt > DAEMON_HEARTBEAT_STALE_MS) return false;
498
- try {
499
- process.kill(lock.pid, 0);
500
- return true;
501
- } catch {
502
- return false;
503
- }
504
- }
505
- function isDevBridgeLockfileFresh(lock, now = Date.now()) {
506
- if (!lock) return false;
507
- if (now - lock.heartbeatAt > DAEMON_HEARTBEAT_STALE_MS) return false;
508
- try {
509
- process.kill(lock.pid, 0);
510
- return true;
511
- } catch {
512
- return false;
513
- }
514
- }
515
-
516
- // ../sootsim-engine/src/metro-fingerprint-registry-client.ts
517
- var import_metro_fingerprint_registry = require("rnxsim/metro-fingerprint-registry");
518
-
519
- // ../sootsim-engine/src/metro-fingerprint.ts
520
- var import_metro_production_bundle = require("rnxsim/metro-production-bundle");
521
- var import_react_native_host_modules = require("rnxsim/react-native-host-modules");
522
-
523
- // src/backend-origin.ts
524
- function normalizeHostname(hostname) {
525
- return hostname.replace(/^\[|\]$/g, "").toLowerCase();
526
- }
527
- function isLoopbackHost(hostname) {
528
- const normalized = normalizeHostname(hostname);
529
- return normalized === "localhost" || normalized.endsWith(".localhost") || normalized === "0.0.0.0" || normalized === "::1" || /^127(?:\.\d{1,3}){3}$/.test(normalized);
530
- }
531
-
532
- // src/cloud-contract.ts
533
- var RNX_CLOUD_MAX_ARTIFACT_BYTES = 20 * 1024 * 1024;
534
- var RNX_CLOUD_DESIGN_MAX_STORAGE_BYTES = 1024 * 1024;
535
- var RNX_CLOUD_COMMAND_TYPES = Object.freeze([
536
- "state",
537
- "query",
538
- "resolve",
539
- "perform",
540
- "waitFor",
541
- "reset",
542
- "screenshot",
543
- "capture",
544
- "captureRegions",
545
- "settle",
546
- "setAppearance",
547
- "openUrl",
548
- "tree",
549
- "tap",
550
- "longPress",
551
- "memory",
552
- "diagnostics"
553
- ]);
554
- var RNX_CLOUD_COMMAND_TYPE_SET = new Set(RNX_CLOUD_COMMAND_TYPES);
555
-
556
- // src/metro-production-bundle.ts
557
- var RNXSIM_METRO_MODULE_PATHS_PREFIX = "globalThis.__sootsimModulePaths=";
558
- var RNX_METRO_MODULE_IDENTITY_VERSION = 2;
559
- var RNXSIM_METRO_SOURCE_MAP_COMMENT = "//# sourceMappingURL=";
560
- function detachRNXMetroSourceMapUrl(bundleText) {
561
- const trimmedEnd = bundleText.replace(/\s+$/, "");
562
- const commentStart = trimmedEnd.lastIndexOf(`
563
- ${RNXSIM_METRO_SOURCE_MAP_COMMENT}`);
564
- if (commentStart < 0) return { source: bundleText, sourceMappingUrl: null };
565
- const url = trimmedEnd.slice(commentStart + 1 + RNXSIM_METRO_SOURCE_MAP_COMMENT.length);
566
- if (url.includes("\n")) return { source: bundleText, sourceMappingUrl: null };
567
- return { source: trimmedEnd.slice(0, commentStart), sourceMappingUrl: url };
568
- }
569
- function createRNXMetroModuleIdentityFooter(identity) {
570
- const payload = JSON.stringify(identity).replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029").replace(/<\//g, "<\\/");
571
- return `
572
- ${RNXSIM_METRO_MODULE_PATHS_PREFIX}${payload};`;
573
- }
574
- function appendRNXMetroModuleIdentityFooter(bundleText, identity) {
575
- const footer = createRNXMetroModuleIdentityFooter(identity);
576
- const detached = detachRNXMetroSourceMapUrl(bundleText);
577
- return detached.sourceMappingUrl === null ? bundleText + footer : `${detached.source}${footer}
578
- ${RNXSIM_METRO_SOURCE_MAP_COMMENT}${detached.sourceMappingUrl}`;
579
- }
580
-
581
- // src/public-brand.ts
582
- var name = "rnx";
583
- var rnxPublicBrand = Object.freeze({
584
- name,
585
- packageName: "rnxsim",
586
- commandName: "rnx",
587
- domain: "rnxsim.com",
588
- origin: "https://rnxsim.com"
589
- });
590
-
591
- // cli/run-rnx.ts
592
- var import_node_async_hooks = require("node:async_hooks");
593
- var sinks = new import_node_async_hooks.AsyncLocalStorage();
594
-
595
- // cli/cloud-session.ts
596
- var MAX_ENCODED_SESSION_BYTES = 32 * 1024;
597
- var RNX_SHELL_UPDATE_PREFIX = "__RNX_SHELL_UPDATE_V1__";
598
- var RNX_SHELL_UPDATE_SET_PREFIX = `${RNX_SHELL_UPDATE_PREFIX}set:`;
599
- var RNX_SHELL_UPDATE_UNSET = `${RNX_SHELL_UPDATE_PREFIX}unset`;
600
-
601
- // cli/cloud-client.ts
602
- var DEFAULT_RNX_CLOUD_ORIGIN = rnxPublicBrand.origin;
603
-
604
905
  // cli/ws-bridge.ts
605
906
  function bridgeWsUrl(wsPort) {
606
907
  return `ws://localhost:${wsPort}`;
@@ -2675,6 +2976,19 @@ function pickSim(sims, wanted, port) {
2675
2976
  return primary;
2676
2977
  }
2677
2978
  async function connect(options = {}) {
2979
+ const target = {
2980
+ simId: options.sim,
2981
+ simIdSource: options.sim ? "flag" : "none"
2982
+ };
2983
+ const cloud = resolveCloudSessionForParsed(target);
2984
+ if (cloud) {
2985
+ const bridge2 = createCloudBridgeForParsed(target);
2986
+ if (!bridge2)
2987
+ throw new SimConnectError("remote simulator session disappeared while connecting");
2988
+ return new SimClient(bridge2, cloud.service === "box" ? cloud.boxId : cloud.simId, {
2989
+ commandTimeoutMs: options.timeoutMs ?? 15e3
2990
+ });
2991
+ }
2678
2992
  const port = options.port ?? resolveBridgeWorld().bridgePort;
2679
2993
  const commandTimeoutMs = options.timeoutMs ?? 15e3;
2680
2994
  const discovery = createBridge(port, { commandTimeoutMs, cliLabel: "sdk" });
@@ -2880,12 +3194,12 @@ function isRnxCaptureRegionAtlas(value) {
2880
3194
 
2881
3195
  // src/vite-plugin-one.ts
2882
3196
  var import_fs4 = __toESM(require("fs"), 1);
2883
- var import_node_crypto3 = require("node:crypto");
3197
+ var import_node_crypto4 = require("node:crypto");
2884
3198
  var import_path3 = __toESM(require("path"), 1);
2885
3199
 
2886
3200
  // src/dev-server-open.ts
2887
3201
  var import_node_child_process = require("node:child_process");
2888
- var import_node_crypto2 = require("node:crypto");
3202
+ var import_node_crypto3 = require("node:crypto");
2889
3203
  var import_node_fs4 = __toESM(require("node:fs"), 1);
2890
3204
  var import_node_path4 = __toESM(require("node:path"), 1);
2891
3205
  var import_node_url = require("node:url");
@@ -2911,7 +3225,7 @@ function tagRnxBundleForPlugin(bundleUrl, replacementOrigin, replacementToken, p
2911
3225
  url.searchParams.set("rnxAppName", appName);
2912
3226
  url.searchParams.set(
2913
3227
  "rnxProjectId",
2914
- (0, import_node_crypto2.createHash)("sha256").update(import_node_fs4.default.realpathSync(projectRoot)).digest("hex").slice(0, 32)
3228
+ (0, import_node_crypto3.createHash)("sha256").update(import_node_fs4.default.realpathSync(projectRoot)).digest("hex").slice(0, 32)
2915
3229
  );
2916
3230
  }
2917
3231
  url.searchParams.set("rnxReplacementOrigin", replacementOrigin);
@@ -3531,7 +3845,7 @@ function rnxPlugin(options = {}) {
3531
3845
  if (options.enabled === false) return;
3532
3846
  const bundleUrl = options.bundleUrl || "/node_modules/one/metro-entry.bundle?platform=ios&dev=true&minify=false";
3533
3847
  const appName = resolveRnxDesktopAppName(options.open, server.config.root);
3534
- const replacementToken = (0, import_node_crypto3.randomUUID)();
3848
+ const replacementToken = (0, import_node_crypto4.randomUUID)();
3535
3849
  const configuredPort = server.config.server.port || 8081;
3536
3850
  const listeningPort = () => {
3537
3851
  const address = server.httpServer?.address();
@@ -3656,7 +3970,7 @@ function rnxPlugin(options = {}) {
3656
3970
  // src/metro-plugin.ts
3657
3971
  var import_fs5 = __toESM(require("fs"), 1);
3658
3972
  var import_module2 = require("module");
3659
- var import_node_crypto4 = require("node:crypto");
3973
+ var import_node_crypto5 = require("node:crypto");
3660
3974
  var import_path4 = __toESM(require("path"), 1);
3661
3975
  var prefix = "/__soot";
3662
3976
  function readCallableMetroExport(moduleValue, label) {
@@ -3731,7 +4045,7 @@ function withRNX(config) {
3731
4045
  const configuredBundleUrl = "/index.bundle?platform=ios&dev=true&hot=true&minify=false";
3732
4046
  const cfgServer = config.server;
3733
4047
  const port = resolveMetroPort(cfgServer);
3734
- const metroServerId = (0, import_node_crypto4.randomUUID)();
4048
+ const metroServerId = (0, import_node_crypto5.randomUUID)();
3735
4049
  const bundleUrl = tagRnxBundleForPlugin(
3736
4050
  configuredBundleUrl,
3737
4051
  `http://localhost:${port}`,