runcloud 0.1.109 → 0.1.111
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 +10 -0
- package/dist/commands/run-cloud.js +97 -6
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/runcloud-0.1.111.tgz +0 -0
- package/skills/run-cloud-ios-simulator/SKILL.md +25 -6
- package/runcloud-0.1.109.tgz +0 -0
package/README.md
CHANGED
|
@@ -67,6 +67,10 @@ runcloud ios open-url myapp://path --id <id> # open a URL or deep
|
|
|
67
67
|
runcloud ios tap <id> 0.5 0.75 # normalized top-left display coordinates
|
|
68
68
|
runcloud ios type-text <id> "Hello from iOS!" # printable US ASCII, tab, and line feed
|
|
69
69
|
runcloud ios screenshot <id> --output ios.png # save a PNG without exposing the viewer URL
|
|
70
|
+
runcloud ios recording start <id> --json # start an idempotent MP4 screen recording
|
|
71
|
+
runcloud ios recording stop <id> <recording-id> --json # stop and finalize it
|
|
72
|
+
runcloud ios recording download <id> <recording-id> \
|
|
73
|
+
--output ios.mp4 --json # download through the authenticated API
|
|
70
74
|
runcloud ios logs <id> --tail 200 # read logs from this lease
|
|
71
75
|
runcloud ios logs <id> --follow # follow new log entries
|
|
72
76
|
runcloud ios delete <id> # release the session
|
|
@@ -96,6 +100,12 @@ report `capsLock`, `numLock`, and `scrollLock` as unsupported keys.
|
|
|
96
100
|
Coordinates are inclusive normalized display coordinates: `(0, 0)` is the
|
|
97
101
|
top-left and `(1, 1)` is the bottom-right.
|
|
98
102
|
|
|
103
|
+
The `recording` group is shared by iOS and Android. It provides `start`,
|
|
104
|
+
`list`, `status` (`get` alias), `stop`, and `download`. Pass a stable
|
|
105
|
+
`--idempotency-key` to retry `start` without creating a second recording.
|
|
106
|
+
`download --output <path>` validates the MP4 and reports its byte size and
|
|
107
|
+
SHA-256 digest without printing a signed storage URL.
|
|
108
|
+
|
|
99
109
|
Interaction commands wait for a correlated simulator acknowledgement. They
|
|
100
110
|
accept `--timeout <milliseconds>` (15 seconds by default), `--request-id <id>`,
|
|
101
111
|
and `--json`. Successful JSON is the API's typed completion envelope. Failures
|
|
@@ -485,6 +485,47 @@ async function captureSimulatorScreenshot(platform, id, opts) {
|
|
|
485
485
|
else
|
|
486
486
|
console.log(`Saved ${platform} simulator screenshot to ${output} (${bytes.byteLength} bytes)`);
|
|
487
487
|
}
|
|
488
|
+
function simulatorRecordingBase(platform, sessionId) {
|
|
489
|
+
return `/run-cloud/${platform}/${encodeURIComponent(sessionId)}/recordings`;
|
|
490
|
+
}
|
|
491
|
+
function recordingIdempotencyKey(value) {
|
|
492
|
+
if (value === undefined)
|
|
493
|
+
return undefined;
|
|
494
|
+
const key = value.trim();
|
|
495
|
+
const hasControlCharacter = [...key].some((character) => {
|
|
496
|
+
const code = character.charCodeAt(0);
|
|
497
|
+
return code <= 31 || code === 127;
|
|
498
|
+
});
|
|
499
|
+
if (!key || key.length > 200 || hasControlCharacter) {
|
|
500
|
+
throw new Error('--idempotency-key must contain 1-200 printable characters');
|
|
501
|
+
}
|
|
502
|
+
return key;
|
|
503
|
+
}
|
|
504
|
+
async function downloadSimulatorRecording(platform, sessionId, recordingId, opts) {
|
|
505
|
+
const bytes = await client().getBinary(`${simulatorRecordingBase(platform, sessionId)}/${encodeURIComponent(recordingId)}/download`);
|
|
506
|
+
if (bytes.length < 8 || bytes.subarray(4, 8).toString('ascii') !== 'ftyp') {
|
|
507
|
+
throw new Error('Simulator recording response was not a valid MP4 file');
|
|
508
|
+
}
|
|
509
|
+
const output = resolve(opts.output);
|
|
510
|
+
mkdirSync(dirname(output), { recursive: true });
|
|
511
|
+
writeFileSync(output, bytes);
|
|
512
|
+
const result = {
|
|
513
|
+
ok: true,
|
|
514
|
+
sessionId,
|
|
515
|
+
recordingId,
|
|
516
|
+
platform,
|
|
517
|
+
action: 'recording.download',
|
|
518
|
+
status: 'completed',
|
|
519
|
+
output,
|
|
520
|
+
contentType: 'video/mp4',
|
|
521
|
+
byteSize: bytes.byteLength,
|
|
522
|
+
sha256: createHash('sha256').update(bytes).digest('hex'),
|
|
523
|
+
};
|
|
524
|
+
if (opts.json)
|
|
525
|
+
console.log(JSON.stringify(result, null, 2));
|
|
526
|
+
else
|
|
527
|
+
console.log(`Saved ${platform} simulator recording to ${output} (${bytes.byteLength} bytes)`);
|
|
528
|
+
}
|
|
488
529
|
function fileBlob(path) {
|
|
489
530
|
const resolved = resolve(path);
|
|
490
531
|
if (!existsSync(resolved))
|
|
@@ -715,7 +756,7 @@ function registerSimulatorCommands(program, platform) {
|
|
|
715
756
|
.addOption(new Option('--dev-host <host>', 'dev-only: target a specific simulator fleet host').hideHelp())
|
|
716
757
|
.action((opts) => action(async () => {
|
|
717
758
|
print(await createSimulatorSession(platform, opts), opts);
|
|
718
|
-
}));
|
|
759
|
+
}, opts));
|
|
719
760
|
simulator
|
|
720
761
|
.command('list')
|
|
721
762
|
.description(`List active ${label} sessions`)
|
|
@@ -724,7 +765,7 @@ function registerSimulatorCommands(program, platform) {
|
|
|
724
765
|
.action((opts) => action(async () => {
|
|
725
766
|
const qs = opts.all ? '?all=1' : '';
|
|
726
767
|
print(await client().get(`/run-cloud/${platform}${qs}`), opts);
|
|
727
|
-
}));
|
|
768
|
+
}, opts));
|
|
728
769
|
simulator
|
|
729
770
|
.command('get')
|
|
730
771
|
.description(`Show ${article} ${label} session`)
|
|
@@ -732,7 +773,7 @@ function registerSimulatorCommands(program, platform) {
|
|
|
732
773
|
.option('--json', 'output JSON', false)
|
|
733
774
|
.action((id, opts) => action(async () => {
|
|
734
775
|
print(await client().get(`/run-cloud/${platform}/${encodeURIComponent(id)}`), opts);
|
|
735
|
-
}));
|
|
776
|
+
}, opts));
|
|
736
777
|
simulator
|
|
737
778
|
.command('delete')
|
|
738
779
|
.description(`Release ${article} ${label} session`)
|
|
@@ -740,7 +781,7 @@ function registerSimulatorCommands(program, platform) {
|
|
|
740
781
|
.option('--json', 'output JSON', false)
|
|
741
782
|
.action((id, opts) => action(async () => {
|
|
742
783
|
print(await client().delete(`/run-cloud/${platform}/${encodeURIComponent(id)}`), opts);
|
|
743
|
-
}));
|
|
784
|
+
}, opts));
|
|
744
785
|
simulator
|
|
745
786
|
.command('open-url')
|
|
746
787
|
.description(`Open a URL or deep link in ${article} ${label} session`)
|
|
@@ -749,7 +790,7 @@ function registerSimulatorCommands(program, platform) {
|
|
|
749
790
|
.option('--json', 'output JSON', false)
|
|
750
791
|
.action((url, opts) => action(async () => {
|
|
751
792
|
print(await client().post(`/run-cloud/${platform}/${encodeURIComponent(opts.id)}/open-url`, { url }), opts);
|
|
752
|
-
}));
|
|
793
|
+
}, opts));
|
|
753
794
|
addInteractionOptions(simulator
|
|
754
795
|
.command('tap')
|
|
755
796
|
.description(`Tap normalized display coordinates in ${article} ${label} session`)
|
|
@@ -882,6 +923,56 @@ function registerSimulatorCommands(program, platform) {
|
|
|
882
923
|
.description(`Capture the current display from ${article} ${label} session`)
|
|
883
924
|
.argument('<id>', 'session id')
|
|
884
925
|
.requiredOption('-o, --output <path>', 'PNG output path')).action((id, opts) => action(() => captureSimulatorScreenshot(platform, id, opts), opts));
|
|
926
|
+
const recording = simulator
|
|
927
|
+
.command('recording')
|
|
928
|
+
.alias('recordings')
|
|
929
|
+
.description(`Record the display of ${article} ${label} session`);
|
|
930
|
+
recording
|
|
931
|
+
.command('start')
|
|
932
|
+
.description(`Start an MP4 screen recording for ${article} active ${label} session`)
|
|
933
|
+
.argument('<id>', 'session id')
|
|
934
|
+
.option('--idempotency-key <key>', 'retry-safe key; reuse it to receive the same recording')
|
|
935
|
+
.option('--json', 'output JSON', false)
|
|
936
|
+
.action((id, opts) => action(async () => {
|
|
937
|
+
const key = recordingIdempotencyKey(opts.idempotencyKey);
|
|
938
|
+
const headers = key ? { 'Idempotency-Key': key } : {};
|
|
939
|
+
print(await client().postWithHeaders(simulatorRecordingBase(platform, id), {}, headers), opts);
|
|
940
|
+
}, opts));
|
|
941
|
+
recording
|
|
942
|
+
.command('list')
|
|
943
|
+
.description(`List retained screen recordings for ${article} ${label} session`)
|
|
944
|
+
.argument('<id>', 'session id')
|
|
945
|
+
.option('--json', 'output JSON', false)
|
|
946
|
+
.action((id, opts) => action(async () => {
|
|
947
|
+
print(await client().get(simulatorRecordingBase(platform, id)), opts);
|
|
948
|
+
}, opts));
|
|
949
|
+
recording
|
|
950
|
+
.command('status')
|
|
951
|
+
.alias('get')
|
|
952
|
+
.description(`Show lifecycle state, failures, events, and retrieval metadata for a recording`)
|
|
953
|
+
.argument('<id>', 'session id')
|
|
954
|
+
.argument('<recording-id>', 'recording id')
|
|
955
|
+
.option('--json', 'output JSON', false)
|
|
956
|
+
.action((id, recordingId, opts) => action(async () => {
|
|
957
|
+
print(await client().get(`${simulatorRecordingBase(platform, id)}/${encodeURIComponent(recordingId)}`), opts);
|
|
958
|
+
}, opts));
|
|
959
|
+
recording
|
|
960
|
+
.command('stop')
|
|
961
|
+
.description(`Stop and finalize a screen recording as an MP4`)
|
|
962
|
+
.argument('<id>', 'session id')
|
|
963
|
+
.argument('<recording-id>', 'recording id')
|
|
964
|
+
.option('--json', 'output JSON', false)
|
|
965
|
+
.action((id, recordingId, opts) => action(async () => {
|
|
966
|
+
print(await client().post(`${simulatorRecordingBase(platform, id)}/${encodeURIComponent(recordingId)}/stop`), opts);
|
|
967
|
+
}, opts));
|
|
968
|
+
recording
|
|
969
|
+
.command('download')
|
|
970
|
+
.description(`Download a ready screen recording through the authenticated API`)
|
|
971
|
+
.argument('<id>', 'session id')
|
|
972
|
+
.argument('<recording-id>', 'recording id')
|
|
973
|
+
.requiredOption('-o, --output <path>', 'MP4 output path')
|
|
974
|
+
.option('--json', 'output file metadata as JSON', false)
|
|
975
|
+
.action((id, recordingId, opts) => action(() => downloadSimulatorRecording(platform, id, recordingId, opts), opts));
|
|
885
976
|
simulator
|
|
886
977
|
.command('logs')
|
|
887
978
|
.description(`Read or follow logs from ${article} active ${label} session`)
|
|
@@ -921,7 +1012,7 @@ function registerSimulatorCommands(program, platform) {
|
|
|
921
1012
|
for (const entry of parsed.entries)
|
|
922
1013
|
writeSimulatorLogEntry(entry, opts.json === true);
|
|
923
1014
|
}
|
|
924
|
-
}));
|
|
1015
|
+
}, opts));
|
|
925
1016
|
return simulator;
|
|
926
1017
|
}
|
|
927
1018
|
function jsonErrorEnvelope(error) {
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const CLI_VERSION = '0.1.
|
|
1
|
+
export const CLI_VERSION = '0.1.111';
|
package/package.json
CHANGED
|
Binary file
|
|
@@ -1,6 +1,6 @@
|
|
|
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 logs, controlling, embedding, smoke-testing, connecting local Metro, taking screenshots, injecting iOS media, or releasing remote mobile sessions.
|
|
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 or screen recordings, injecting iOS media, or releasing remote mobile sessions.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Operate run.cloud Mobile Sessions
|
|
@@ -79,6 +79,22 @@ controls or the `capsLock`, `numLock`, and `scrollLock` keys. Handle structured
|
|
|
79
79
|
An acknowledgement means input dispatch completed. Confirm visible app effects
|
|
80
80
|
with a screenshot or the signed viewer when the outcome matters.
|
|
81
81
|
|
|
82
|
+
## Record the Screen
|
|
83
|
+
|
|
84
|
+
Both platforms expose the same session-scoped recording lifecycle:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
RECORDING_ID=$(runcloud ios recording start "$SESSION_ID" \
|
|
88
|
+
--idempotency-key "task-${TASK_ID:-manual}" --json | jq -r '.id')
|
|
89
|
+
runcloud ios recording stop "$SESSION_ID" "$RECORDING_ID" --json
|
|
90
|
+
runcloud ios recording download "$SESSION_ID" "$RECORDING_ID" \
|
|
91
|
+
--output ./recording.mp4 --json
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Use `recording list` and `recording status` to inspect retained state,
|
|
95
|
+
lifecycle events, and actionable failures. Replace `ios` with `android` for an
|
|
96
|
+
Android session. Reuse the idempotency key when retrying `start`.
|
|
97
|
+
|
|
82
98
|
## Use the TypeScript SDK
|
|
83
99
|
|
|
84
100
|
```bash
|
|
@@ -116,11 +132,14 @@ typed acknowledgements. Use `RunCloudError` fields such as `code`, `retryable`,
|
|
|
116
132
|
`requestId`, and `action` when reporting API failures.
|
|
117
133
|
|
|
118
134
|
The lifecycle surface also includes `create`, `list`, `get`, `openUrl`, `logs`,
|
|
119
|
-
`followLogs`, and `delete`. Both platforms expose `screenshot
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
135
|
+
`followLogs`, and `delete`. Both platforms expose `screenshot`,
|
|
136
|
+
`startRecording`, `listRecordings`, `getRecording`, `stopRecording`, and
|
|
137
|
+
`downloadRecording`; iOS additionally supports `uploadVideo` and
|
|
138
|
+
`uploadMicrophoneAudio`. Inspect the installed types for complete create,
|
|
139
|
+
asset, log, recording, and media options.
|
|
140
|
+
|
|
141
|
+
In compact form, `cloud.ios`: `create`, `list`, `get`, `openUrl`, `logs`,
|
|
142
|
+
`followLogs`, `screenshot`, and the five recording methods above.
|
|
124
143
|
`cloud.android` provides the same shared lifecycle and control operations.
|
|
125
144
|
|
|
126
145
|
## Diagnose App Failures
|
package/runcloud-0.1.109.tgz
DELETED
|
Binary file
|