runcloud 0.1.107 → 0.1.109
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -0
- package/dist/api.js +111 -37
- package/dist/commands/run-cloud.js +591 -8
- package/dist/version.js +1 -1
- package/examples/eight-device-mosaic/demo.mjs +12 -1
- package/examples/eight-device-mosaic/verify.mjs +5 -2
- package/examples/live-camera-relay/demo.mjs +8 -1
- package/examples/live-camera-relay/verify.mjs +5 -2
- package/package.json +1 -1
- package/runcloud-0.1.109.tgz +0 -0
- package/skills/run-cloud-ios-simulator/SKILL.md +101 -126
|
@@ -71,9 +71,14 @@ function escapeHtml(value) {
|
|
|
71
71
|
.replaceAll('>', '>');
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
function scriptData(value) {
|
|
75
|
+
return JSON.stringify(value).replaceAll('<', '\\u003c');
|
|
76
|
+
}
|
|
77
|
+
|
|
74
78
|
function embedUrl(value) {
|
|
75
79
|
const url = new URL(value);
|
|
76
80
|
url.searchParams.set('embed', '1');
|
|
81
|
+
url.searchParams.set('loadingGuard', '1');
|
|
77
82
|
return url.toString();
|
|
78
83
|
}
|
|
79
84
|
|
|
@@ -130,6 +135,7 @@ export function mosaicViewerHtml(sessions, deadline = Date.now() + DEMO_LEASE_MS
|
|
|
130
135
|
const deadline = ${deadline};
|
|
131
136
|
const tiles = [...document.querySelectorAll('.tile')];
|
|
132
137
|
const frames = [...document.querySelectorAll('iframe')];
|
|
138
|
+
const frameOrigins = ${scriptData(sessions.map((session) => new URL(session.url).origin))};
|
|
133
139
|
const ready = new Set();
|
|
134
140
|
const count = document.querySelector('#ready-count');
|
|
135
141
|
const timer = document.querySelector('#timer');
|
|
@@ -141,7 +147,12 @@ export function mosaicViewerHtml(sessions, deadline = Date.now() + DEMO_LEASE_MS
|
|
|
141
147
|
setInterval(tick, 250);
|
|
142
148
|
window.addEventListener('message', (event) => {
|
|
143
149
|
const index = frames.findIndex((frame) => frame.contentWindow === event.source);
|
|
144
|
-
if (
|
|
150
|
+
if (
|
|
151
|
+
index < 0
|
|
152
|
+
|| event.origin !== frameOrigins[index]
|
|
153
|
+
|| !event.data
|
|
154
|
+
|| typeof event.data !== 'object'
|
|
155
|
+
) return;
|
|
145
156
|
if (event.data.type === 'ios-simulator:status' && event.data.streaming === true) {
|
|
146
157
|
ready.add(index);
|
|
147
158
|
tiles[index].dataset.ready = 'true';
|
|
@@ -35,8 +35,11 @@ test('renders eight signed embeds without printing them into the viewer URL', as
|
|
|
35
35
|
}));
|
|
36
36
|
const html = mosaicViewerHtml(sessions, 601_000);
|
|
37
37
|
assert.equal((html.match(/<iframe /g) || []).length, 8);
|
|
38
|
-
assert.match(
|
|
39
|
-
assert.
|
|
38
|
+
assert.equal((html.match(/embed=1/g) || []).length, 8);
|
|
39
|
+
assert.equal((html.match(/loadingGuard=1/g) || []).length, 8);
|
|
40
|
+
assert.match(html, /const frameOrigins = \["https:\/\/sim\.example"/);
|
|
41
|
+
assert.match(html, /frame\.contentWindow === event\.source/);
|
|
42
|
+
assert.match(html, /event\.origin !== frameOrigins\[index\]/);
|
|
40
43
|
assert.match(html, /const deadline = 601000/);
|
|
41
44
|
|
|
42
45
|
const viewer = await startMosaicViewer(sessions, 601_000);
|
|
@@ -87,6 +87,7 @@ function scriptData(value) {
|
|
|
87
87
|
function embedUrl(value) {
|
|
88
88
|
const url = new URL(value);
|
|
89
89
|
url.searchParams.set('embed', '1');
|
|
90
|
+
url.searchParams.set('loadingGuard', '1');
|
|
90
91
|
return url.toString();
|
|
91
92
|
}
|
|
92
93
|
|
|
@@ -193,6 +194,7 @@ export function cameraViewerHtml(sessions, duration, roomId, now = Date.now()) {
|
|
|
193
194
|
const deadline = ${deadline};
|
|
194
195
|
const devices = [...document.querySelectorAll('.device')];
|
|
195
196
|
const frames = [...document.querySelectorAll('iframe')];
|
|
197
|
+
const frameOrigins = ${scriptData(sessions.map((session) => new URL(session.url).origin))};
|
|
196
198
|
const ready = new Set();
|
|
197
199
|
const count = document.querySelector('#ready-count');
|
|
198
200
|
const timer = document.querySelector('#timer');
|
|
@@ -287,7 +289,12 @@ export function cameraViewerHtml(sessions, duration, roomId, now = Date.now()) {
|
|
|
287
289
|
setInterval(tick, 250);
|
|
288
290
|
window.addEventListener('message', (event) => {
|
|
289
291
|
const index = frames.findIndex((frame) => frame.contentWindow === event.source);
|
|
290
|
-
if (
|
|
292
|
+
if (
|
|
293
|
+
index < 0
|
|
294
|
+
|| event.origin !== frameOrigins[index]
|
|
295
|
+
|| !event.data
|
|
296
|
+
|| typeof event.data !== 'object'
|
|
297
|
+
) return;
|
|
291
298
|
if (event.data.type === 'ios-simulator:status' && event.data.streaming === true) {
|
|
292
299
|
ready.add(index);
|
|
293
300
|
devices[index].dataset.ready = 'true';
|
|
@@ -67,8 +67,11 @@ test('renders three signed embeds while keeping credentials out of the local URL
|
|
|
67
67
|
const demoSessions = sessions();
|
|
68
68
|
const html = cameraViewerHtml(demoSessions, 60, 'room-12345678', 1_000);
|
|
69
69
|
assert.equal((html.match(/<iframe /g) || []).length, 3);
|
|
70
|
-
assert.match(
|
|
71
|
-
assert.
|
|
70
|
+
assert.equal((html.match(/embed=1/g) || []).length, 3);
|
|
71
|
+
assert.equal((html.match(/loadingGuard=1/g) || []).length, 3);
|
|
72
|
+
assert.match(html, /const frameOrigins = \["https:\/\/sim\.example"/);
|
|
73
|
+
assert.match(html, /frame\.contentWindow === event\.source/);
|
|
74
|
+
assert.match(html, /event\.origin !== frameOrigins\[index\]/);
|
|
72
75
|
assert.match(html, /Connect webcam/);
|
|
73
76
|
assert.match(html, /com\.apple\.mobilesafari/);
|
|
74
77
|
assert.match(html, /audio: false/);
|
package/package.json
CHANGED
|
Binary file
|
|
@@ -1,44 +1,32 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: run-cloud-ios-simulator
|
|
3
|
-
description: Operate run.cloud iOS simulator and Android emulator sessions with the CLI or TypeScript SDK. Use for creating, installing, inspecting, reading
|
|
3
|
+
description: Operate run.cloud iOS simulator and Android emulator sessions with the CLI or TypeScript SDK. Use for creating, installing, inspecting, reading logs, controlling, embedding, smoke-testing, connecting local Metro, taking screenshots, injecting iOS media, or releasing remote mobile sessions.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Operate run.cloud Mobile Sessions
|
|
7
7
|
|
|
8
|
-
Use
|
|
9
|
-
`@run-cloud/
|
|
8
|
+
Use the `runcloud` CLI for terminal workflows, `@run-cloud/sdk` for TypeScript,
|
|
9
|
+
and `@run-cloud/ui` for React embeds.
|
|
10
10
|
|
|
11
11
|
## Authenticate
|
|
12
12
|
|
|
13
13
|
- Install the CLI with `npm install -g runcloud`.
|
|
14
|
-
- Use `runcloud login`
|
|
15
|
-
`runcloud login --manual` when a local callback cannot open.
|
|
14
|
+
- Use `runcloud login`; add `--manual` when a local callback cannot open.
|
|
16
15
|
- In CI, set `RUN_CLOUD_API_KEY`. `RUN_CLOUD_API_TOKEN` is an equivalent alias.
|
|
17
|
-
- Set `RUN_CLOUD_API_URL` only to override
|
|
18
|
-
|
|
19
|
-
- Never print, commit, or place credentials in a skill file.
|
|
20
|
-
|
|
21
|
-
- Require Node.js 20 or newer for the CLI and TypeScript SDK.
|
|
16
|
+
- Set `RUN_CLOUD_API_URL` only to override `https://api.run.cloud`.
|
|
17
|
+
- Require Node.js 20 or newer.
|
|
18
|
+
- Never print, commit, or place credentials in a skill file. Treat signed session
|
|
19
|
+
and tunnel URLs as bearer secrets.
|
|
22
20
|
|
|
23
|
-
|
|
21
|
+
Check access and credit before starting metered work:
|
|
24
22
|
|
|
25
23
|
```bash
|
|
26
24
|
runcloud account --json
|
|
27
25
|
```
|
|
28
26
|
|
|
29
|
-
|
|
30
|
-
available fleet capacity. A create request may queue while capacity is full.
|
|
27
|
+
The SDK equivalents are `cloud.account()` and `cloud.usage({ orgId? })`.
|
|
31
28
|
|
|
32
|
-
##
|
|
33
|
-
|
|
34
|
-
- Prefer CLI commands with `--json` for shell automation.
|
|
35
|
-
- Prefer `@run-cloud/sdk` for TypeScript applications and CI.
|
|
36
|
-
- Inspect `runcloud <command> --help` or installed SDK types before using a
|
|
37
|
-
method not documented here.
|
|
38
|
-
|
|
39
|
-
## Use the CLI
|
|
40
|
-
|
|
41
|
-
Create an iOS session, capture its ID, open a deep link, and release it:
|
|
29
|
+
## Create and Release Sessions
|
|
42
30
|
|
|
43
31
|
```bash
|
|
44
32
|
SESSION_ID=$(runcloud ios create \
|
|
@@ -48,79 +36,51 @@ SESSION_ID=$(runcloud ios create \
|
|
|
48
36
|
--json | jq -r '.id')
|
|
49
37
|
|
|
50
38
|
trap 'runcloud ios delete "$SESSION_ID" >/dev/null 2>&1 || true' EXIT
|
|
51
|
-
|
|
52
39
|
runcloud ios get "$SESSION_ID" --json
|
|
53
|
-
runcloud ios open-url myapp://settings --id "$SESSION_ID" --json
|
|
54
40
|
```
|
|
55
41
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
- `runcloud ios|android create`
|
|
61
|
-
- `runcloud ios|android list [--all]`
|
|
62
|
-
- `runcloud ios|android get <id>`
|
|
63
|
-
- `runcloud ios|android open-url <url> --id <id>`
|
|
64
|
-
- `runcloud ios|android logs <id> [--tail N|--follow]`
|
|
65
|
-
- `runcloud ios|android delete <id>`
|
|
66
|
-
|
|
67
|
-
Create accepts `--model`, `--region`, `--display-name`, repeatable `--label`,
|
|
68
|
-
repeatable `--install`, repeatable `--install-asset`,
|
|
69
|
-
`--inactivity-timeout`, `--hard-timeout`, `--codec`, `--rm`, and `--json`.
|
|
70
|
-
|
|
71
|
-
Use assets and samples when no local artifact is ready:
|
|
72
|
-
|
|
73
|
-
```bash
|
|
74
|
-
runcloud sample download ios
|
|
75
|
-
runcloud ios create --install ./run-cloud-sample-ios.app.tar.gz --json
|
|
76
|
-
|
|
77
|
-
runcloud asset push ./build/MyApp.tar.gz --name my-app --json
|
|
78
|
-
runcloud ios create --install-asset my-app --json
|
|
79
|
-
runcloud asset list --json
|
|
80
|
-
runcloud asset pull <asset-id> --output ./MyApp.tar.gz
|
|
81
|
-
runcloud asset delete <asset-id> --json
|
|
82
|
-
```
|
|
42
|
+
Replace `ios` with `android` for an Android artifact. The shared lifecycle is
|
|
43
|
+
`create`, `list`, `get`, `open-url`, `logs`, and `delete`. Use `--json` whenever
|
|
44
|
+
another program consumes output, and inspect `runcloud ios|android --help` for
|
|
45
|
+
create and log options.
|
|
83
46
|
|
|
84
47
|
iOS needs an Apple Silicon simulator-compatible `.app`, `.zip`, `.tar.gz`, or
|
|
85
|
-
`.ipa
|
|
86
|
-
|
|
48
|
+
`.ipa`; a device-signed App Store IPA is not a substitute. Android needs an
|
|
49
|
+
emulator-compatible APK.
|
|
87
50
|
|
|
88
|
-
##
|
|
51
|
+
## Control a Session
|
|
89
52
|
|
|
90
|
-
|
|
53
|
+
Both platform groups expose acknowledged controls:
|
|
91
54
|
|
|
92
55
|
```bash
|
|
93
|
-
runcloud ios
|
|
94
|
-
runcloud
|
|
56
|
+
runcloud ios tap "$SESSION_ID" 0.5 0.3 --json
|
|
57
|
+
runcloud ios swipe "$SESSION_ID" 0.5 0.8 0.5 0.2 --duration 300 --json
|
|
58
|
+
runcloud ios type-text "$SESSION_ID" 'hello' --json
|
|
59
|
+
runcloud ios press-key "$SESSION_ID" enter --json
|
|
60
|
+
runcloud ios press-button "$SESSION_ID" home --json
|
|
61
|
+
runcloud ios screenshot "$SESSION_ID" --output ios.png --json
|
|
95
62
|
```
|
|
96
63
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
64
|
+
The full control set is `tap`, `swipe`, `gesture`, `type-text`, `press-key`,
|
|
65
|
+
`press-button`, `rotate`, `reload`, `scroll`, `toggle-software-keyboard`,
|
|
66
|
+
`simulate-memory-warning`, `rotate-digital-crown`, `set-render-debug`, and
|
|
67
|
+
`screenshot`. Every interaction accepts `--request-id`, `--timeout`, and
|
|
68
|
+
`--json`; use `runcloud <platform> <control> --help` for its typed arguments.
|
|
102
69
|
|
|
103
|
-
|
|
70
|
+
Coordinates use the current display orientation: `(0, 0)` is top-left and
|
|
71
|
+
`(1, 1)` is bottom-right. Gesture steps use one or two points with `begin`,
|
|
72
|
+
`move`, and `end` phases. Each `delayMs` is the pause before the next step, so
|
|
73
|
+
the final `end` step must use `0`. Key names are semantic US-keyboard names;
|
|
74
|
+
modifiers are `shift`, `control`, `alt`, and `meta`. Current mobile sessions do
|
|
75
|
+
not support Digital Crown input, and Android does not support iOS render-debug
|
|
76
|
+
controls or the `capsLock`, `numLock`, and `scrollLock` keys. Handle structured
|
|
77
|
+
`unsupported_action` errors instead of retrying them.
|
|
104
78
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
```bash
|
|
108
|
-
runcloud ios tunnel "$SESSION_ID" \
|
|
109
|
-
--local-port 8081 \
|
|
110
|
-
--service metro \
|
|
111
|
-
--json
|
|
112
|
-
|
|
113
|
-
runcloud ios tunnel-status --json
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
Use the run.cloud sidecar flow. Do not install or expose an unauthenticated
|
|
117
|
-
third-party tunnel. If the sidecar is unavailable, report that requirement
|
|
118
|
-
instead of guessing a public URL.
|
|
79
|
+
An acknowledgement means input dispatch completed. Confirm visible app effects
|
|
80
|
+
with a screenshot or the signed viewer when the outcome matters.
|
|
119
81
|
|
|
120
82
|
## Use the TypeScript SDK
|
|
121
83
|
|
|
122
|
-
Install the SDK:
|
|
123
|
-
|
|
124
84
|
```bash
|
|
125
85
|
npm install @run-cloud/sdk
|
|
126
86
|
```
|
|
@@ -132,81 +92,96 @@ import { writeFile } from "node:fs/promises";
|
|
|
132
92
|
import { Client } from "@run-cloud/sdk";
|
|
133
93
|
|
|
134
94
|
const cloud = new Client();
|
|
135
|
-
const session = await cloud.
|
|
136
|
-
displayName: "Agent smoke",
|
|
137
|
-
labels: { owner: "agent" },
|
|
95
|
+
const session = await cloud.android.create({
|
|
138
96
|
inactivityTimeout: "60s",
|
|
139
97
|
hardTimeout: "10m",
|
|
140
|
-
|
|
98
|
+
tags: { owner: "agent" },
|
|
141
99
|
});
|
|
142
100
|
|
|
143
101
|
try {
|
|
144
|
-
await cloud.
|
|
145
|
-
|
|
146
|
-
await
|
|
102
|
+
await cloud.android.tap(session.id, { x: 0.5, y: 0.3 });
|
|
103
|
+
await cloud.android.typeText(session.id, "hello");
|
|
104
|
+
await cloud.android.pressKey(session.id, "enter");
|
|
105
|
+
const screenshot = await cloud.android.screenshot(session.id);
|
|
106
|
+
await writeFile("android.png", screenshot);
|
|
147
107
|
} finally {
|
|
148
|
-
await cloud.
|
|
108
|
+
await cloud.android.delete(session.id);
|
|
149
109
|
}
|
|
150
110
|
```
|
|
151
111
|
|
|
152
|
-
|
|
112
|
+
`cloud.ios`, `cloud.android`, and the platform-selectable `cloud.simulators`
|
|
113
|
+
expose `interact` plus convenience methods matching every CLI control above.
|
|
114
|
+
Interaction options accept `requestId`, `timeoutMs`, and `signal`; results are
|
|
115
|
+
typed acknowledgements. Use `RunCloudError` fields such as `code`, `retryable`,
|
|
116
|
+
`requestId`, and `action` when reporting API failures.
|
|
153
117
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
- `cloud.simulators`: runtime-platform `create`, `list`, `get`, `openUrl`,
|
|
159
|
-
`delete`
|
|
160
|
-
- `cloud.assets`: `upload`, `list`, `delete`
|
|
118
|
+
The lifecycle surface also includes `create`, `list`, `get`, `openUrl`, `logs`,
|
|
119
|
+
`followLogs`, and `delete`. Both platforms expose `screenshot`; iOS additionally
|
|
120
|
+
supports `uploadVideo` and `uploadMicrophoneAudio`. Inspect the installed types
|
|
121
|
+
for complete create, asset, log, and media options.
|
|
161
122
|
|
|
162
|
-
|
|
163
|
-
`
|
|
123
|
+
In compact form, `cloud.ios`: `create`, `list`, `get`, `openUrl`, `logs`, `followLogs`, `screenshot`.
|
|
124
|
+
`cloud.android` provides the same shared lifecycle and control operations.
|
|
125
|
+
|
|
126
|
+
## Diagnose App Failures
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
runcloud ios logs "$SESSION_ID" --tail 1000
|
|
130
|
+
runcloud android logs "$SESSION_ID" --tail 1000
|
|
131
|
+
```
|
|
164
132
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
separate from the public SDK.
|
|
133
|
+
Use `--follow` while reproducing an issue. Before releasing a failed session,
|
|
134
|
+
capture a bounded retained snapshot; a follow stream contains only new entries.
|
|
168
135
|
|
|
169
|
-
##
|
|
136
|
+
## Connect Local Development
|
|
170
137
|
|
|
171
|
-
|
|
172
|
-
stores a user-owned asset and imports it into Photos.
|
|
138
|
+
Connect a local Metro or mock server through the supported sidecar flow:
|
|
173
139
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
140
|
+
```bash
|
|
141
|
+
runcloud ios tunnel "$SESSION_ID" --local-port 8081 --service metro --json
|
|
142
|
+
runcloud ios tunnel-status --json
|
|
143
|
+
```
|
|
178
144
|
|
|
179
|
-
|
|
145
|
+
Do not expose an unauthenticated third-party tunnel. If the sidecar is
|
|
146
|
+
unavailable, report that requirement instead of guessing a public URL.
|
|
180
147
|
|
|
181
148
|
## Embed a Session
|
|
182
149
|
|
|
183
|
-
-
|
|
184
|
-
-
|
|
185
|
-
|
|
186
|
-
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
`
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
150
|
+
- Use `RemoteControl` from `@run-cloud/ui` with the signed session URL.
|
|
151
|
+
- Its ref exposes promise-based `interact` and convenience methods matching the
|
|
152
|
+
SDK controls. Handle `onInteractionResult` for inspectable acknowledgements.
|
|
153
|
+
- Add `embed=1` to raw iframe URLs. Add `loadingGuard=1` when interaction should
|
|
154
|
+
wait for streaming and app readiness.
|
|
155
|
+
- Raw iframe requests use `run-cloud:interaction`; acknowledgements use
|
|
156
|
+
`run-cloud:interaction-result`. Correlate them by `requestId`. To stop a
|
|
157
|
+
pending request, post `run-cloud:interaction-cancel` with the same
|
|
158
|
+
`requestId` and `action`.
|
|
159
|
+
- Verify `event.source` and the exact signed-URL origin, and use that origin as
|
|
160
|
+
the `postMessage` target.
|
|
161
|
+
- Legacy `ios-simulator:command` messages remain compatibility-only. Prefer the
|
|
162
|
+
generic acknowledged interaction channel for new code.
|
|
163
|
+
- Create a new session after an `ios-simulator:session-restart-requested`
|
|
164
|
+
message; never reuse an ended URL.
|
|
165
|
+
|
|
166
|
+
## Assets, Samples, and Demos
|
|
195
167
|
|
|
196
168
|
```bash
|
|
169
|
+
runcloud sample download ios
|
|
170
|
+
runcloud asset push ./build/MyApp.tar.gz --name my-app --json
|
|
171
|
+
runcloud asset pull <asset-id>
|
|
172
|
+
runcloud ios create --install-asset my-app --json
|
|
197
173
|
runcloud demo run eight-device-mosaic --open
|
|
198
174
|
runcloud demo run live-camera-relay --open
|
|
199
175
|
```
|
|
200
176
|
|
|
201
|
-
|
|
177
|
+
Delete uploaded assets when no longer needed. Bundled demos release their
|
|
178
|
+
sessions automatically.
|
|
202
179
|
|
|
203
180
|
## Guardrails
|
|
204
181
|
|
|
205
|
-
- Release every session created during a task unless
|
|
206
|
-
to keep it open.
|
|
182
|
+
- Release every session created during a task unless asked to keep it open.
|
|
207
183
|
- Use inactivity and hard timeouts for unattended work.
|
|
208
|
-
- Verify platform compatibility before changing
|
|
184
|
+
- Verify artifact/platform compatibility before changing app code after an
|
|
209
185
|
install failure.
|
|
210
186
|
- Do not expose credentials, signed viewer URLs, tunnel URLs, or simulator
|
|
211
187
|
tokens in logs, screenshots, PR comments, or chat output.
|
|
212
|
-
- Do not claim that browser iframe controls are public SDK methods.
|