rnxsim 0.1.513 → 0.1.515

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 (60) hide show
  1. package/cli/cloud-client.ts +18 -8
  2. package/cli/commands/inspect/get-layout.ts +2 -1
  3. package/cli/outbound-endpoints.ts +1 -7
  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 +64 -1
  15. package/dist-lib/bridge-contract-input.mjs +63 -1
  16. package/dist-lib/bridge-contract.cjs +73 -3
  17. package/dist-lib/bridge-contract.mjs +69 -2
  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 +117 -22
  22. package/dist-lib/cloud-contract.mjs +104 -21
  23. package/dist-lib/cloud.cjs +121 -27
  24. package/dist-lib/cloud.mjs +121 -27
  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 +45 -20
  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 +62 -24
  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 +62 -24
  54. package/dist-lib/swift.cjs +1 -1
  55. package/dist-lib/vite.cjs +1 -1
  56. package/package.json +2 -1
  57. package/src/bridge-contract-input.ts +74 -0
  58. package/src/bridge-contract.ts +103 -0
  59. package/src/cloud-contract.ts +191 -19
  60. package/src/cloud.ts +104 -7
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.513 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.515 | (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 __defProp = Object.defineProperty;
@@ -22,13 +22,25 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
22
22
  // src/cloud-contract.ts
23
23
  var cloud_contract_exports = {};
24
24
  __export(cloud_contract_exports, {
25
+ RNX_CLOUD_ALL_COMMAND_SCOPES: () => RNX_CLOUD_ALL_COMMAND_SCOPES,
26
+ RNX_CLOUD_COMMAND_SCOPES: () => RNX_CLOUD_COMMAND_SCOPES,
25
27
  RNX_CLOUD_COMMAND_TYPES: () => RNX_CLOUD_COMMAND_TYPES,
26
28
  RNX_CLOUD_DESIGN_MAX_STORAGE_BYTES: () => RNX_CLOUD_DESIGN_MAX_STORAGE_BYTES,
27
29
  RNX_CLOUD_DESIGN_MAX_STORAGE_ENTRIES: () => RNX_CLOUD_DESIGN_MAX_STORAGE_ENTRIES,
30
+ RNX_CLOUD_LABELS_MAX_COUNT: () => RNX_CLOUD_LABELS_MAX_COUNT,
31
+ RNX_CLOUD_LABEL_MAX_LENGTH: () => RNX_CLOUD_LABEL_MAX_LENGTH,
28
32
  RNX_CLOUD_MAX_ARTIFACT_BYTES: () => RNX_CLOUD_MAX_ARTIFACT_BYTES,
29
33
  RNX_CLOUD_TEST_FILES_MAX_BYTES: () => RNX_CLOUD_TEST_FILES_MAX_BYTES,
30
34
  RNX_CLOUD_TEST_FILES_MAX_COUNT: () => RNX_CLOUD_TEST_FILES_MAX_COUNT,
31
- isRnxCloudCommandType: () => isRnxCloudCommandType
35
+ RNX_CLOUD_TOKEN_SCOPES: () => RNX_CLOUD_TOKEN_SCOPES,
36
+ isRnxCloudCommandType: () => isRnxCloudCommandType,
37
+ isRnxCloudSimStatus: () => isRnxCloudSimStatus,
38
+ isValidRnxCloudLabel: () => isValidRnxCloudLabel,
39
+ matchesRnxCloudLabels: () => matchesRnxCloudLabels,
40
+ parseRnxCloudLabels: () => parseRnxCloudLabels,
41
+ readRnxCloudLabels: () => readRnxCloudLabels,
42
+ rnxCloudCommandScope: () => rnxCloudCommandScope,
43
+ rnxCloudScopesAllowCommand: () => rnxCloudScopesAllowCommand
32
44
  });
33
45
  module.exports = __toCommonJS(cloud_contract_exports);
34
46
  var RNX_CLOUD_MAX_ARTIFACT_BYTES = 20 * 1024 * 1024;
@@ -36,36 +48,119 @@ var RNX_CLOUD_DESIGN_MAX_STORAGE_ENTRIES = 256;
36
48
  var RNX_CLOUD_DESIGN_MAX_STORAGE_BYTES = 1024 * 1024;
37
49
  var RNX_CLOUD_TEST_FILES_MAX_COUNT = 256;
38
50
  var RNX_CLOUD_TEST_FILES_MAX_BYTES = 5e5;
39
- var RNX_CLOUD_COMMAND_TYPES = Object.freeze([
40
- "state",
41
- "query",
42
- "resolve",
43
- "perform",
44
- "waitFor",
45
- "reset",
46
- "screenshot",
47
- "capture",
48
- "captureRegions",
49
- "settle",
50
- "setAppearance",
51
- "openUrl",
52
- "tree",
53
- "tap",
54
- "longPress",
55
- "memory",
56
- "diagnostics"
57
- ]);
51
+ var RNX_CLOUD_COMMAND_SCOPES = {
52
+ state: "read",
53
+ tree: "read",
54
+ query: "read",
55
+ resolve: "read",
56
+ screenshot: "read",
57
+ capture: "read",
58
+ captureRegions: "read",
59
+ memory: "read",
60
+ diagnostics: "read",
61
+ storageSnapshot: "read",
62
+ perform: "drive",
63
+ waitFor: "drive",
64
+ tap: "drive",
65
+ longPress: "drive",
66
+ settle: "drive",
67
+ openUrl: "drive",
68
+ setAppearance: "drive",
69
+ keyboard: "drive",
70
+ focus: "drive",
71
+ flowStatus: "drive",
72
+ reset: "reset",
73
+ camera: "media",
74
+ // string evaluation and arbitrary bridge calls are maestro's boundary: the
75
+ // customer sdk never carries this scope, and the service grants it only to
76
+ // the account api key caller. close rides along, since ending the page ends
77
+ // the sim and belongs to the sim's owner rather than any driver.
78
+ evaluate: "debug",
79
+ call: "debug",
80
+ close: "debug"
81
+ };
82
+ function scopeTableKeys() {
83
+ return Object.keys(RNX_CLOUD_COMMAND_SCOPES).filter(
84
+ (key) => Object.prototype.hasOwnProperty.call(RNX_CLOUD_COMMAND_SCOPES, key)
85
+ );
86
+ }
87
+ var RNX_CLOUD_COMMAND_TYPES = Object.freeze(scopeTableKeys());
58
88
  var RNX_CLOUD_COMMAND_TYPE_SET = new Set(RNX_CLOUD_COMMAND_TYPES);
59
89
  function isRnxCloudCommandType(value) {
60
90
  return RNX_CLOUD_COMMAND_TYPE_SET.has(value);
61
91
  }
92
+ function rnxCloudCommandScope(type) {
93
+ return RNX_CLOUD_COMMAND_SCOPES[type];
94
+ }
95
+ var RNX_CLOUD_TOKEN_SCOPES = Object.freeze([
96
+ "read",
97
+ "drive",
98
+ "reset",
99
+ "media"
100
+ ]);
101
+ var RNX_CLOUD_ALL_COMMAND_SCOPES = Object.freeze(["read", "drive", "reset", "media", "debug"]);
102
+ function rnxCloudScopesAllowCommand(scopes, type) {
103
+ return scopes.includes(RNX_CLOUD_COMMAND_SCOPES[type]);
104
+ }
105
+ var RNX_CLOUD_LABELS_MAX_COUNT = 16;
106
+ var RNX_CLOUD_LABEL_MAX_LENGTH = 63;
107
+ var RNX_CLOUD_LABEL_CONTROL = /[\u0000-\u001f\u007f]/;
108
+ function isValidRnxCloudLabel(key, value) {
109
+ return key.length > 0 && key.length <= RNX_CLOUD_LABEL_MAX_LENGTH && value.length <= RNX_CLOUD_LABEL_MAX_LENGTH && !key.includes(",") && !key.includes("=") && !value.includes(",") && !RNX_CLOUD_LABEL_CONTROL.test(key) && !RNX_CLOUD_LABEL_CONTROL.test(value);
110
+ }
111
+ function parseRnxCloudLabels(value) {
112
+ if (value === "") return {};
113
+ const entries = value.split(",");
114
+ if (entries.length > RNX_CLOUD_LABELS_MAX_COUNT) return null;
115
+ const labels = {};
116
+ for (const entry of entries) {
117
+ const separator = entry.indexOf("=");
118
+ if (separator < 1) return null;
119
+ const key = entry.slice(0, separator).trim();
120
+ const label = entry.slice(separator + 1).trim();
121
+ if (!isValidRnxCloudLabel(key, label)) return null;
122
+ if (Object.hasOwn(labels, key)) return null;
123
+ labels[key] = label;
124
+ }
125
+ return labels;
126
+ }
127
+ function readRnxCloudLabels(value) {
128
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
129
+ const entries = Object.entries(value);
130
+ if (entries.length > RNX_CLOUD_LABELS_MAX_COUNT) return null;
131
+ const labels = {};
132
+ for (const [key, label] of entries) {
133
+ if (typeof label !== "string") return null;
134
+ if (!isValidRnxCloudLabel(key, label)) return null;
135
+ labels[key] = label;
136
+ }
137
+ return labels;
138
+ }
139
+ function matchesRnxCloudLabels(labels, selector) {
140
+ return Object.entries(selector).every(([key, value]) => labels[key] === value);
141
+ }
142
+ function isRnxCloudSimStatus(value) {
143
+ return value === "creating" || value === "open" || value === "hibernated" || value === "deleting" || value === "deleted";
144
+ }
62
145
  // Annotate the CommonJS export names for ESM import in node:
63
146
  0 && (module.exports = {
147
+ RNX_CLOUD_ALL_COMMAND_SCOPES,
148
+ RNX_CLOUD_COMMAND_SCOPES,
64
149
  RNX_CLOUD_COMMAND_TYPES,
65
150
  RNX_CLOUD_DESIGN_MAX_STORAGE_BYTES,
66
151
  RNX_CLOUD_DESIGN_MAX_STORAGE_ENTRIES,
152
+ RNX_CLOUD_LABELS_MAX_COUNT,
153
+ RNX_CLOUD_LABEL_MAX_LENGTH,
67
154
  RNX_CLOUD_MAX_ARTIFACT_BYTES,
68
155
  RNX_CLOUD_TEST_FILES_MAX_BYTES,
69
156
  RNX_CLOUD_TEST_FILES_MAX_COUNT,
70
- isRnxCloudCommandType
157
+ RNX_CLOUD_TOKEN_SCOPES,
158
+ isRnxCloudCommandType,
159
+ isRnxCloudSimStatus,
160
+ isValidRnxCloudLabel,
161
+ matchesRnxCloudLabels,
162
+ parseRnxCloudLabels,
163
+ readRnxCloudLabels,
164
+ rnxCloudCommandScope,
165
+ rnxCloudScopesAllowCommand
71
166
  });
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.513 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.515 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/cloud-contract.ts
4
4
  var RNX_CLOUD_MAX_ARTIFACT_BYTES = 20 * 1024 * 1024;
@@ -6,35 +6,118 @@ var RNX_CLOUD_DESIGN_MAX_STORAGE_ENTRIES = 256;
6
6
  var RNX_CLOUD_DESIGN_MAX_STORAGE_BYTES = 1024 * 1024;
7
7
  var RNX_CLOUD_TEST_FILES_MAX_COUNT = 256;
8
8
  var RNX_CLOUD_TEST_FILES_MAX_BYTES = 5e5;
9
- var RNX_CLOUD_COMMAND_TYPES = Object.freeze([
10
- "state",
11
- "query",
12
- "resolve",
13
- "perform",
14
- "waitFor",
15
- "reset",
16
- "screenshot",
17
- "capture",
18
- "captureRegions",
19
- "settle",
20
- "setAppearance",
21
- "openUrl",
22
- "tree",
23
- "tap",
24
- "longPress",
25
- "memory",
26
- "diagnostics"
27
- ]);
9
+ var RNX_CLOUD_COMMAND_SCOPES = {
10
+ state: "read",
11
+ tree: "read",
12
+ query: "read",
13
+ resolve: "read",
14
+ screenshot: "read",
15
+ capture: "read",
16
+ captureRegions: "read",
17
+ memory: "read",
18
+ diagnostics: "read",
19
+ storageSnapshot: "read",
20
+ perform: "drive",
21
+ waitFor: "drive",
22
+ tap: "drive",
23
+ longPress: "drive",
24
+ settle: "drive",
25
+ openUrl: "drive",
26
+ setAppearance: "drive",
27
+ keyboard: "drive",
28
+ focus: "drive",
29
+ flowStatus: "drive",
30
+ reset: "reset",
31
+ camera: "media",
32
+ // string evaluation and arbitrary bridge calls are maestro's boundary: the
33
+ // customer sdk never carries this scope, and the service grants it only to
34
+ // the account api key caller. close rides along, since ending the page ends
35
+ // the sim and belongs to the sim's owner rather than any driver.
36
+ evaluate: "debug",
37
+ call: "debug",
38
+ close: "debug"
39
+ };
40
+ function scopeTableKeys() {
41
+ return Object.keys(RNX_CLOUD_COMMAND_SCOPES).filter(
42
+ (key) => Object.prototype.hasOwnProperty.call(RNX_CLOUD_COMMAND_SCOPES, key)
43
+ );
44
+ }
45
+ var RNX_CLOUD_COMMAND_TYPES = Object.freeze(scopeTableKeys());
28
46
  var RNX_CLOUD_COMMAND_TYPE_SET = new Set(RNX_CLOUD_COMMAND_TYPES);
29
47
  function isRnxCloudCommandType(value) {
30
48
  return RNX_CLOUD_COMMAND_TYPE_SET.has(value);
31
49
  }
50
+ function rnxCloudCommandScope(type) {
51
+ return RNX_CLOUD_COMMAND_SCOPES[type];
52
+ }
53
+ var RNX_CLOUD_TOKEN_SCOPES = Object.freeze([
54
+ "read",
55
+ "drive",
56
+ "reset",
57
+ "media"
58
+ ]);
59
+ var RNX_CLOUD_ALL_COMMAND_SCOPES = Object.freeze(["read", "drive", "reset", "media", "debug"]);
60
+ function rnxCloudScopesAllowCommand(scopes, type) {
61
+ return scopes.includes(RNX_CLOUD_COMMAND_SCOPES[type]);
62
+ }
63
+ var RNX_CLOUD_LABELS_MAX_COUNT = 16;
64
+ var RNX_CLOUD_LABEL_MAX_LENGTH = 63;
65
+ var RNX_CLOUD_LABEL_CONTROL = /[\u0000-\u001f\u007f]/;
66
+ function isValidRnxCloudLabel(key, value) {
67
+ return key.length > 0 && key.length <= RNX_CLOUD_LABEL_MAX_LENGTH && value.length <= RNX_CLOUD_LABEL_MAX_LENGTH && !key.includes(",") && !key.includes("=") && !value.includes(",") && !RNX_CLOUD_LABEL_CONTROL.test(key) && !RNX_CLOUD_LABEL_CONTROL.test(value);
68
+ }
69
+ function parseRnxCloudLabels(value) {
70
+ if (value === "") return {};
71
+ const entries = value.split(",");
72
+ if (entries.length > RNX_CLOUD_LABELS_MAX_COUNT) return null;
73
+ const labels = {};
74
+ for (const entry of entries) {
75
+ const separator = entry.indexOf("=");
76
+ if (separator < 1) return null;
77
+ const key = entry.slice(0, separator).trim();
78
+ const label = entry.slice(separator + 1).trim();
79
+ if (!isValidRnxCloudLabel(key, label)) return null;
80
+ if (Object.hasOwn(labels, key)) return null;
81
+ labels[key] = label;
82
+ }
83
+ return labels;
84
+ }
85
+ function readRnxCloudLabels(value) {
86
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
87
+ const entries = Object.entries(value);
88
+ if (entries.length > RNX_CLOUD_LABELS_MAX_COUNT) return null;
89
+ const labels = {};
90
+ for (const [key, label] of entries) {
91
+ if (typeof label !== "string") return null;
92
+ if (!isValidRnxCloudLabel(key, label)) return null;
93
+ labels[key] = label;
94
+ }
95
+ return labels;
96
+ }
97
+ function matchesRnxCloudLabels(labels, selector) {
98
+ return Object.entries(selector).every(([key, value]) => labels[key] === value);
99
+ }
100
+ function isRnxCloudSimStatus(value) {
101
+ return value === "creating" || value === "open" || value === "hibernated" || value === "deleting" || value === "deleted";
102
+ }
32
103
  export {
104
+ RNX_CLOUD_ALL_COMMAND_SCOPES,
105
+ RNX_CLOUD_COMMAND_SCOPES,
33
106
  RNX_CLOUD_COMMAND_TYPES,
34
107
  RNX_CLOUD_DESIGN_MAX_STORAGE_BYTES,
35
108
  RNX_CLOUD_DESIGN_MAX_STORAGE_ENTRIES,
109
+ RNX_CLOUD_LABELS_MAX_COUNT,
110
+ RNX_CLOUD_LABEL_MAX_LENGTH,
36
111
  RNX_CLOUD_MAX_ARTIFACT_BYTES,
37
112
  RNX_CLOUD_TEST_FILES_MAX_BYTES,
38
113
  RNX_CLOUD_TEST_FILES_MAX_COUNT,
39
- isRnxCloudCommandType
114
+ RNX_CLOUD_TOKEN_SCOPES,
115
+ isRnxCloudCommandType,
116
+ isRnxCloudSimStatus,
117
+ isValidRnxCloudLabel,
118
+ matchesRnxCloudLabels,
119
+ parseRnxCloudLabels,
120
+ readRnxCloudLabels,
121
+ rnxCloudCommandScope,
122
+ rnxCloudScopesAllowCommand
40
123
  };
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.513 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.515 | (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 __defProp = Object.defineProperty;
@@ -99,29 +99,60 @@ function isRnxScreenCapture(value) {
99
99
  // src/cloud-contract.ts
100
100
  var RNX_CLOUD_MAX_ARTIFACT_BYTES = 20 * 1024 * 1024;
101
101
  var RNX_CLOUD_DESIGN_MAX_STORAGE_BYTES = 1024 * 1024;
102
- var RNX_CLOUD_COMMAND_TYPES = Object.freeze([
103
- "state",
104
- "query",
105
- "resolve",
106
- "perform",
107
- "waitFor",
108
- "reset",
109
- "screenshot",
110
- "capture",
111
- "captureRegions",
112
- "settle",
113
- "setAppearance",
114
- "openUrl",
115
- "tree",
116
- "tap",
117
- "longPress",
118
- "memory",
119
- "diagnostics"
120
- ]);
102
+ var RNX_CLOUD_COMMAND_SCOPES = {
103
+ state: "read",
104
+ tree: "read",
105
+ query: "read",
106
+ resolve: "read",
107
+ screenshot: "read",
108
+ capture: "read",
109
+ captureRegions: "read",
110
+ memory: "read",
111
+ diagnostics: "read",
112
+ storageSnapshot: "read",
113
+ perform: "drive",
114
+ waitFor: "drive",
115
+ tap: "drive",
116
+ longPress: "drive",
117
+ settle: "drive",
118
+ openUrl: "drive",
119
+ setAppearance: "drive",
120
+ keyboard: "drive",
121
+ focus: "drive",
122
+ flowStatus: "drive",
123
+ reset: "reset",
124
+ camera: "media",
125
+ // string evaluation and arbitrary bridge calls are maestro's boundary: the
126
+ // customer sdk never carries this scope, and the service grants it only to
127
+ // the account api key caller. close rides along, since ending the page ends
128
+ // the sim and belongs to the sim's owner rather than any driver.
129
+ evaluate: "debug",
130
+ call: "debug",
131
+ close: "debug"
132
+ };
133
+ function scopeTableKeys() {
134
+ return Object.keys(RNX_CLOUD_COMMAND_SCOPES).filter(
135
+ (key) => Object.prototype.hasOwnProperty.call(RNX_CLOUD_COMMAND_SCOPES, key)
136
+ );
137
+ }
138
+ var RNX_CLOUD_COMMAND_TYPES = Object.freeze(scopeTableKeys());
121
139
  var RNX_CLOUD_COMMAND_TYPE_SET = new Set(RNX_CLOUD_COMMAND_TYPES);
122
140
  function isRnxCloudCommandType(value) {
123
141
  return RNX_CLOUD_COMMAND_TYPE_SET.has(value);
124
142
  }
143
+ function rnxCloudCommandScope(type) {
144
+ return RNX_CLOUD_COMMAND_SCOPES[type];
145
+ }
146
+ var RNX_CLOUD_TOKEN_SCOPES = Object.freeze([
147
+ "read",
148
+ "drive",
149
+ "reset",
150
+ "media"
151
+ ]);
152
+ var RNX_CLOUD_ALL_COMMAND_SCOPES = Object.freeze(["read", "drive", "reset", "media", "debug"]);
153
+ function rnxCloudScopesAllowCommand(scopes, type) {
154
+ return scopes.includes(RNX_CLOUD_COMMAND_SCOPES[type]);
155
+ }
125
156
 
126
157
  // src/public-brand.ts
127
158
  var name = "rnx";
@@ -136,6 +167,8 @@ var rnxPublicBrand = Object.freeze({
136
167
  // src/cloud.ts
137
168
  var DEFAULT_DEVICE = "iphone-16";
138
169
  var DEFAULT_WAIT_READY_MS = 2e4;
170
+ var CLOUD_MAX_RETRIES = 2;
171
+ var CLOUD_NEVER_RETRY_CODES = /* @__PURE__ */ new Set(["concurrency_limit", "early_access"]);
139
172
  function isRecord(value) {
140
173
  return value !== null && typeof value === "object" && !Array.isArray(value);
141
174
  }
@@ -173,6 +206,62 @@ function responseError(status, text) {
173
206
  `RNX Cloud request failed (${status})${text.trim() ? `: ${text.trim()}` : ""}`
174
207
  );
175
208
  }
209
+ function readCloudErrorCode(text) {
210
+ let parsed;
211
+ try {
212
+ parsed = JSON.parse(text);
213
+ } catch {
214
+ return null;
215
+ }
216
+ if (!isRecord(parsed)) return null;
217
+ if (typeof parsed.error === "string") return parsed.error;
218
+ if (isRecord(parsed.error) && typeof parsed.error.code === "string") {
219
+ return parsed.error.code;
220
+ }
221
+ return null;
222
+ }
223
+ function readRetryAfterMs(response, text) {
224
+ const header = response.headers.get("retry-after");
225
+ if (header !== null) {
226
+ const seconds = Number(header.trim());
227
+ if (Number.isFinite(seconds) && seconds >= 0) return seconds * 1e3;
228
+ }
229
+ let parsed;
230
+ try {
231
+ parsed = JSON.parse(text);
232
+ } catch {
233
+ return null;
234
+ }
235
+ if (isRecord(parsed) && typeof parsed.retryAfterMs === "number" && Number.isFinite(parsed.retryAfterMs) && parsed.retryAfterMs >= 0) {
236
+ return parsed.retryAfterMs;
237
+ }
238
+ return null;
239
+ }
240
+ function sleep(ms) {
241
+ return new Promise((resolve) => setTimeout(resolve, ms));
242
+ }
243
+ async function fetchWithCloudRetry(url, init, options) {
244
+ let attempt = 0;
245
+ for (; ; ) {
246
+ const response = await fetch(
247
+ url,
248
+ options?.timeoutMs === void 0 ? init : { ...init, signal: AbortSignal.timeout(options.timeoutMs) }
249
+ );
250
+ if (response.ok) return response;
251
+ const retryableStatus = response.status === 429 || response.status >= 500;
252
+ if (!retryableStatus || attempt >= CLOUD_MAX_RETRIES) return response;
253
+ const text = await response.text();
254
+ const code = readCloudErrorCode(text);
255
+ const retryable = response.status >= 500 ? !(code !== null && CLOUD_NEVER_RETRY_CODES.has(code)) : code === "rate_limited";
256
+ if (!retryable) {
257
+ return new Response(text, { status: response.status, headers: response.headers });
258
+ }
259
+ if (response.status === 429) {
260
+ await sleep(readRetryAfterMs(response, text) ?? 1e3);
261
+ }
262
+ attempt += 1;
263
+ }
264
+ }
176
265
  function copyArrayBuffer(bytes) {
177
266
  const copy = new ArrayBuffer(bytes.byteLength);
178
267
  new Uint8Array(copy).set(bytes);
@@ -255,6 +344,11 @@ function cloudCommand(command) {
255
344
  if (!isRnxCloudCommandType(command.type)) {
256
345
  throw new Error(`RNX Cloud does not support bridge command type ${command.type}`);
257
346
  }
347
+ if (!rnxCloudScopesAllowCommand(RNX_CLOUD_TOKEN_SCOPES, command.type)) {
348
+ throw new Error(
349
+ `RNX Cloud command ${command.type} requires the ${rnxCloudCommandScope(command.type)} scope`
350
+ );
351
+ }
258
352
  return { ...clean, type: command.type };
259
353
  }
260
354
  var RemoteSimulator = class {
@@ -321,7 +415,7 @@ var RemoteSimulator = class {
321
415
  };
322
416
  let response;
323
417
  try {
324
- response = await fetch(
418
+ response = await fetchWithCloudRetry(
325
419
  `${this.session.origin}/v1/sims/${encodeURIComponent(this.session.simId)}/commands`,
326
420
  {
327
421
  method: "POST",
@@ -329,9 +423,9 @@ var RemoteSimulator = class {
329
423
  authorization: `Bearer ${this.session.simulatorToken}`,
330
424
  "content-type": "application/json"
331
425
  },
332
- body: JSON.stringify(body),
333
- ...options?.timeoutMs ? { signal: AbortSignal.timeout(options.timeoutMs) } : {}
334
- }
426
+ body: JSON.stringify(body)
427
+ },
428
+ options?.timeoutMs === void 0 ? void 0 : { timeoutMs: options.timeoutMs }
335
429
  );
336
430
  } catch (error) {
337
431
  throw new Error(
@@ -356,7 +450,7 @@ var RemoteSimulator = class {
356
450
  return parsed.result;
357
451
  }
358
452
  async confirm() {
359
- const response = await fetch(
453
+ const response = await fetchWithCloudRetry(
360
454
  `${this.session.origin}/v1/sims/${encodeURIComponent(this.session.simId)}/claim`,
361
455
  {
362
456
  method: "POST",
@@ -383,7 +477,7 @@ var RemoteSimulator = class {
383
477
  return claim;
384
478
  }
385
479
  async close() {
386
- const response = await fetch(
480
+ const response = await fetchWithCloudRetry(
387
481
  `${this.session.origin}/v1/boxes/${encodeURIComponent(this.session.boxId)}`,
388
482
  {
389
483
  method: "DELETE",
@@ -414,7 +508,7 @@ async function createRemoteSimulator(input) {
414
508
  const device = input.device?.trim() || DEFAULT_DEVICE;
415
509
  let response;
416
510
  try {
417
- response = await fetch(`${origin}/v1/boxes`, {
511
+ response = await fetchWithCloudRetry(`${origin}/v1/boxes`, {
418
512
  method: "POST",
419
513
  headers: {
420
514
  authorization: input.authorization,