runcloud 0.1.115 → 0.1.117
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 +25 -0
- package/dist/api.js +4 -2
- package/dist/commands/run-cloud.js +229 -3
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/runcloud-0.1.117.tgz +0 -0
- package/skills/run-cloud-ios-simulator/SKILL.md +16 -4
- package/runcloud-0.1.115.tgz +0 -0
package/README.md
CHANGED
|
@@ -66,6 +66,7 @@ runcloud ios get <id> # inspect a session (
|
|
|
66
66
|
runcloud ios open-url myapp://path --id <id> # open a URL or deep link
|
|
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
|
+
runcloud ios accessibility-tree <id> --json # read the current typed accessibility hierarchy
|
|
69
70
|
runcloud ios screenshot <id> --output ios.png # save a PNG without exposing the viewer URL
|
|
70
71
|
runcloud ios recording start <id> --json # start an idempotent MP4 screen recording
|
|
71
72
|
runcloud ios recording stop <id> <recording-id> --json # stop and finalize it
|
|
@@ -79,9 +80,27 @@ runcloud ios delete <id> # release the session
|
|
|
79
80
|
runcloud android create --model pixel
|
|
80
81
|
runcloud android swipe <id> 0.5 0.8 0.5 0.2 --duration 250
|
|
81
82
|
runcloud android press-key <id> enter
|
|
83
|
+
runcloud android accessibility-tree <id> --json
|
|
82
84
|
runcloud android screenshot <id> --output android.png --json
|
|
83
85
|
```
|
|
84
86
|
|
|
87
|
+
`open-url` accepts HTTPS URLs and application deep links on both platforms. Keep
|
|
88
|
+
the complete target quoted so its URI delimiters stay in one argument:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
runcloud ios open-url \
|
|
92
|
+
'runcloudproof://open/items%2F42?message=hello%20world&return=https%3A%2F%2Fexample.com%2Fdone%3Fx%3D1%26y%3Dtwo#proof' \
|
|
93
|
+
--id <id> \
|
|
94
|
+
--json
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Success returns `ok`, `platform`, `sessionId`, `device`, `leaseId`, and the
|
|
98
|
+
exact input `url`. Replace `ios` with `android` for an emulator. See
|
|
99
|
+
[Open URLs and Deep Links](https://docs.run.cloud/platform/open-urls) for SDK
|
|
100
|
+
and REST examples, platform behavior, validation, and troubleshooting.
|
|
101
|
+
On iOS, a first custom-scheme handoff can still require confirming the system's
|
|
102
|
+
**Open in “App”?** prompt after the success acknowledgement.
|
|
103
|
+
|
|
85
104
|
Useful `create` flags: `--region`, `--display-name`, `--inactivity-timeout 3m`,
|
|
86
105
|
`--hard-timeout 1h`, `--install-asset <name-or-id>`, `--rm` (release when the
|
|
87
106
|
command exits), `--json`.
|
|
@@ -100,6 +119,12 @@ report `capsLock`, `numLock`, and `scrollLock` as unsupported keys.
|
|
|
100
119
|
Coordinates are inclusive normalized display coordinates: `(0, 0)` is the
|
|
101
120
|
top-left and `(1, 1)` is the bottom-right.
|
|
102
121
|
|
|
122
|
+
`accessibility-tree` (`accessibility` alias) returns an indented terminal tree
|
|
123
|
+
or a versioned JSON hierarchy with cross-platform roles, labels, values,
|
|
124
|
+
states, bounds, identifiers, and nested children. Secure field values are
|
|
125
|
+
always redacted. The command reads only the authenticated active session and
|
|
126
|
+
never releases it; use `--timeout` to change the 20-second client wait.
|
|
127
|
+
|
|
103
128
|
The `recording` group is shared by iOS and Android. It provides `start`,
|
|
104
129
|
`list`, `status` (`get` alias), `stop`, and `download`. Pass a stable
|
|
105
130
|
`--idempotency-key` to retry `start` without creating a second recording.
|
package/dist/api.js
CHANGED
|
@@ -10,6 +10,7 @@ export class ApiError extends Error {
|
|
|
10
10
|
sessionId;
|
|
11
11
|
platform;
|
|
12
12
|
action;
|
|
13
|
+
suggestedAction;
|
|
13
14
|
interactionStatus;
|
|
14
15
|
acceptedAt;
|
|
15
16
|
completedAt;
|
|
@@ -65,6 +66,7 @@ function parsedError(body) {
|
|
|
65
66
|
...(typeof parsed.sessionId === 'string' ? { sessionId: parsed.sessionId } : {}),
|
|
66
67
|
...(platform ? { platform } : {}),
|
|
67
68
|
...(typeof parsed.action === 'string' ? { action: parsed.action } : {}),
|
|
69
|
+
...(typeof nested?.action === 'string' ? { suggestedAction: nested.action } : {}),
|
|
68
70
|
...(interactionStatus ? { interactionStatus } : {}),
|
|
69
71
|
...(typeof parsed.acceptedAt === 'string' ? { acceptedAt: parsed.acceptedAt } : {}),
|
|
70
72
|
...(typeof parsed.completedAt === 'string' ? { completedAt: parsed.completedAt } : {}),
|
|
@@ -114,8 +116,8 @@ export class ApiClient {
|
|
|
114
116
|
const contentType = res.headers.get('content-type') ?? '';
|
|
115
117
|
return contentType.includes('application/json') ? res.json() : res;
|
|
116
118
|
}
|
|
117
|
-
get(path) {
|
|
118
|
-
return this.request('GET', path);
|
|
119
|
+
get(path, options = {}) {
|
|
120
|
+
return this.request('GET', path, undefined, options);
|
|
119
121
|
}
|
|
120
122
|
async getText(path) {
|
|
121
123
|
const response = await this.request('GET', path);
|
|
@@ -53,8 +53,19 @@ export const SIMULATOR_SCREENSHOT_ERROR_CODES = [
|
|
|
53
53
|
'screenshot_invalid_response',
|
|
54
54
|
'screenshot_failed',
|
|
55
55
|
];
|
|
56
|
+
export const SIMULATOR_ACCESSIBILITY_ERROR_CODES = [
|
|
57
|
+
'active_session_not_found',
|
|
58
|
+
'simulator_capacity_unavailable',
|
|
59
|
+
'simulator_session_ended',
|
|
60
|
+
'accessibility_unavailable',
|
|
61
|
+
'accessibility_cancelled',
|
|
62
|
+
'accessibility_timeout',
|
|
63
|
+
'accessibility_transport_error',
|
|
64
|
+
'accessibility_invalid_response',
|
|
65
|
+
];
|
|
56
66
|
const simulatorInteractionErrorCodeSet = new Set(SIMULATOR_INTERACTION_ERROR_CODES);
|
|
57
67
|
const simulatorScreenshotErrorCodeSet = new Set(SIMULATOR_SCREENSHOT_ERROR_CODES);
|
|
68
|
+
const simulatorAccessibilityErrorCodeSet = new Set(SIMULATOR_ACCESSIBILITY_ERROR_CODES);
|
|
58
69
|
function normalizedInteractionErrorCode(value) {
|
|
59
70
|
if (typeof value === 'string' && simulatorInteractionErrorCodeSet.has(value)) {
|
|
60
71
|
return value;
|
|
@@ -85,7 +96,13 @@ function normalizedScreenshotErrorCode(value) {
|
|
|
85
96
|
? value
|
|
86
97
|
: 'screenshot_failed';
|
|
87
98
|
}
|
|
99
|
+
function normalizedAccessibilityErrorCode(value) {
|
|
100
|
+
return typeof value === 'string' && simulatorAccessibilityErrorCodeSet.has(value)
|
|
101
|
+
? value
|
|
102
|
+
: 'accessibility_unavailable';
|
|
103
|
+
}
|
|
88
104
|
const DEFAULT_INTERACTION_TIMEOUT_MS = 15_000;
|
|
105
|
+
const DEFAULT_ACCESSIBILITY_TIMEOUT_MS = 20_000;
|
|
89
106
|
const INTERACTION_RESPONSE_MARGIN_MS = 250;
|
|
90
107
|
const REQUEST_ID_PATTERN = /^[A-Za-z0-9._:-]{1,128}$/;
|
|
91
108
|
export const RUN_CLOUD_SKILL_NAMES = [
|
|
@@ -392,10 +409,21 @@ class CliInteractionError extends Error {
|
|
|
392
409
|
}
|
|
393
410
|
}
|
|
394
411
|
async function withInteractionCancellation(timeoutMs, requestId, actionName, sessionId, platform, operation, run) {
|
|
412
|
+
const operationLabel = operation === 'interaction' ? 'interaction' : operation;
|
|
413
|
+
const cancelledCode = operation === 'screenshot'
|
|
414
|
+
? 'screenshot_cancelled'
|
|
415
|
+
: operation === 'accessibility'
|
|
416
|
+
? 'accessibility_cancelled'
|
|
417
|
+
: 'interaction_cancelled';
|
|
418
|
+
const timeoutCode = operation === 'screenshot'
|
|
419
|
+
? 'screenshot_timeout'
|
|
420
|
+
: operation === 'accessibility'
|
|
421
|
+
? 'accessibility_timeout'
|
|
422
|
+
: 'interaction_timeout';
|
|
395
423
|
const controller = new AbortController();
|
|
396
|
-
const onSigint = () => controller.abort(new CliInteractionError(`Simulator ${
|
|
424
|
+
const onSigint = () => controller.abort(new CliInteractionError(`Simulator ${operationLabel} cancelled by SIGINT`, cancelledCode, requestId, actionName, sessionId, platform));
|
|
397
425
|
process.once('SIGINT', onSigint);
|
|
398
|
-
const timer = setTimeout(() => controller.abort(new CliInteractionError(`Simulator ${actionName} did not complete within ${timeoutMs} ms`,
|
|
426
|
+
const timer = setTimeout(() => controller.abort(new CliInteractionError(`Simulator ${actionName} did not complete within ${timeoutMs} ms`, timeoutCode, requestId, actionName, sessionId, platform)), timeoutMs + INTERACTION_RESPONSE_MARGIN_MS);
|
|
399
427
|
try {
|
|
400
428
|
return await run(controller.signal);
|
|
401
429
|
}
|
|
@@ -485,6 +513,143 @@ async function captureSimulatorScreenshot(platform, id, opts) {
|
|
|
485
513
|
else
|
|
486
514
|
console.log(`Saved ${platform} simulator screenshot to ${output} (${bytes.byteLength} bytes)`);
|
|
487
515
|
}
|
|
516
|
+
function validatedAccessibilitySnapshot(value, platform) {
|
|
517
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
518
|
+
throw new Error('accessibility response must be an object');
|
|
519
|
+
}
|
|
520
|
+
const snapshot = value;
|
|
521
|
+
if (snapshot.schemaVersion !== 1 || snapshot.platform !== platform) {
|
|
522
|
+
throw new Error('accessibility schema or platform did not match the request');
|
|
523
|
+
}
|
|
524
|
+
if (!Array.isArray(snapshot.roots))
|
|
525
|
+
throw new Error('accessibility roots must be an array');
|
|
526
|
+
if (typeof snapshot.nodeCount !== 'number'
|
|
527
|
+
|| !Number.isInteger(snapshot.nodeCount)
|
|
528
|
+
|| snapshot.nodeCount < 0
|
|
529
|
+
|| snapshot.nodeCount > 500) {
|
|
530
|
+
throw new Error('accessibility nodeCount must be an integer from 0 to 500');
|
|
531
|
+
}
|
|
532
|
+
if (!snapshot.screen || typeof snapshot.screen !== 'object' || Array.isArray(snapshot.screen)) {
|
|
533
|
+
throw new Error('accessibility screen must be an object');
|
|
534
|
+
}
|
|
535
|
+
const screen = snapshot.screen;
|
|
536
|
+
if (screen.unit !== (platform === 'ios' ? 'points' : 'pixels')) {
|
|
537
|
+
throw new Error('accessibility screen unit did not match the platform');
|
|
538
|
+
}
|
|
539
|
+
const ids = new Set();
|
|
540
|
+
let count = 0;
|
|
541
|
+
const visit = (candidate, path, depth) => {
|
|
542
|
+
if (depth > 80 || count >= 500 || !candidate || typeof candidate !== 'object' || Array.isArray(candidate)) {
|
|
543
|
+
throw new Error('accessibility tree exceeded its schema bounds');
|
|
544
|
+
}
|
|
545
|
+
const node = candidate;
|
|
546
|
+
if (typeof node.id !== 'string' || !node.id || ids.has(node.id)) {
|
|
547
|
+
throw new Error('accessibility node identifiers must be non-empty and unique');
|
|
548
|
+
}
|
|
549
|
+
if (node.path !== path || typeof node.role !== 'string' || !Array.isArray(node.children)) {
|
|
550
|
+
throw new Error('accessibility node structure is invalid');
|
|
551
|
+
}
|
|
552
|
+
const states = node.states && typeof node.states === 'object' && !Array.isArray(node.states)
|
|
553
|
+
? node.states
|
|
554
|
+
: null;
|
|
555
|
+
const native = node.native && typeof node.native === 'object' && !Array.isArray(node.native)
|
|
556
|
+
? node.native
|
|
557
|
+
: null;
|
|
558
|
+
if (!states || !native || native.platform !== platform) {
|
|
559
|
+
throw new Error('accessibility node state or platform detail is invalid');
|
|
560
|
+
}
|
|
561
|
+
const secure = states.secure === true
|
|
562
|
+
|| native.password === true
|
|
563
|
+
|| (platform === 'ios' && /(?:secure|password)/iu.test([
|
|
564
|
+
native.type,
|
|
565
|
+
native.roleDescription,
|
|
566
|
+
native.subrole,
|
|
567
|
+
].filter((part) => typeof part === 'string').join(' ')));
|
|
568
|
+
if (secure) {
|
|
569
|
+
states.secure = true;
|
|
570
|
+
node.value = null;
|
|
571
|
+
}
|
|
572
|
+
ids.add(node.id);
|
|
573
|
+
count += 1;
|
|
574
|
+
node.children.forEach((child, index) => visit(child, `${path}.${index}`, depth + 1));
|
|
575
|
+
};
|
|
576
|
+
snapshot.roots.forEach((root, index) => visit(root, String(index), 0));
|
|
577
|
+
if (count !== snapshot.nodeCount)
|
|
578
|
+
throw new Error('accessibility nodeCount did not match the tree');
|
|
579
|
+
return snapshot;
|
|
580
|
+
}
|
|
581
|
+
function printAccessibilitySnapshot(snapshot) {
|
|
582
|
+
const platform = String(snapshot.platform);
|
|
583
|
+
const screen = snapshot.screen;
|
|
584
|
+
console.log(`${platform} accessibility tree: ${String(snapshot.nodeCount)} nodes, `
|
|
585
|
+
+ `${String(screen.width)}x${String(screen.height)} ${String(screen.unit)}`
|
|
586
|
+
+ `${snapshot.truncated === true ? ' (truncated)' : ''}`);
|
|
587
|
+
const visit = (candidate, depth) => {
|
|
588
|
+
const node = candidate;
|
|
589
|
+
const states = node.states;
|
|
590
|
+
const bounds = node.bounds && typeof node.bounds === 'object'
|
|
591
|
+
? node.bounds
|
|
592
|
+
: null;
|
|
593
|
+
const stateLabels = [
|
|
594
|
+
states.enabled === false ? 'disabled' : null,
|
|
595
|
+
states.focused === true ? 'focused' : null,
|
|
596
|
+
states.selected === true ? 'selected' : null,
|
|
597
|
+
states.checked === true ? 'checked' : states.checked === 'mixed' ? 'mixed' : null,
|
|
598
|
+
states.expanded === true ? 'expanded' : states.expanded === false ? 'collapsed' : null,
|
|
599
|
+
states.secure === true ? 'secure' : null,
|
|
600
|
+
].filter(Boolean);
|
|
601
|
+
const label = typeof node.label === 'string' && node.label ? ` "${node.label}"` : '';
|
|
602
|
+
const identifier = typeof node.identifier === 'string' && node.identifier
|
|
603
|
+
? ` #${node.identifier}`
|
|
604
|
+
: '';
|
|
605
|
+
const value = typeof node.value === 'string' && node.value
|
|
606
|
+
? ` = ${JSON.stringify(node.value.slice(0, 120))}`
|
|
607
|
+
: '';
|
|
608
|
+
const frame = bounds
|
|
609
|
+
? ` @ ${String(bounds.x)},${String(bounds.y)} ${String(bounds.width)}x${String(bounds.height)}`
|
|
610
|
+
: '';
|
|
611
|
+
const state = stateLabels.length ? ` [${stateLabels.join(', ')}]` : '';
|
|
612
|
+
console.log(`${' '.repeat(depth)}${String(node.role)}${label}${identifier}${value}${state}${frame}`);
|
|
613
|
+
for (const child of node.children)
|
|
614
|
+
visit(child, depth + 1);
|
|
615
|
+
};
|
|
616
|
+
for (const root of snapshot.roots)
|
|
617
|
+
visit(root, 0);
|
|
618
|
+
}
|
|
619
|
+
async function captureSimulatorAccessibilityTree(platform, id, opts) {
|
|
620
|
+
const timeoutMs = integerInRange(opts.timeout, '--timeout', 100, 60_000, DEFAULT_ACCESSIBILITY_TIMEOUT_MS);
|
|
621
|
+
const requestId = `cli_${randomUUID()}`;
|
|
622
|
+
let value;
|
|
623
|
+
try {
|
|
624
|
+
value = await withInteractionCancellation(timeoutMs, requestId, 'accessibility', id, platform, 'accessibility', async (signal) => await client().get(`/run-cloud/${platform}/${encodeURIComponent(id)}/accessibility`, { signal }));
|
|
625
|
+
}
|
|
626
|
+
catch (error) {
|
|
627
|
+
if (error instanceof ApiError) {
|
|
628
|
+
Object.assign(error, {
|
|
629
|
+
code: normalizedAccessibilityErrorCode(error.code),
|
|
630
|
+
requestId: error.requestId ?? requestId,
|
|
631
|
+
sessionId: error.sessionId ?? id,
|
|
632
|
+
platform: error.platform ?? platform,
|
|
633
|
+
action: error.action ?? 'accessibility',
|
|
634
|
+
});
|
|
635
|
+
}
|
|
636
|
+
if (error instanceof ApiTransportError) {
|
|
637
|
+
throw new CliInteractionError('Simulator accessibility could not reach the run.cloud API', 'accessibility_transport_error', requestId, 'accessibility', id, platform);
|
|
638
|
+
}
|
|
639
|
+
throw error;
|
|
640
|
+
}
|
|
641
|
+
let snapshot;
|
|
642
|
+
try {
|
|
643
|
+
snapshot = validatedAccessibilitySnapshot(value, platform);
|
|
644
|
+
}
|
|
645
|
+
catch {
|
|
646
|
+
throw new CliInteractionError('Simulator accessibility returned an invalid tree', 'accessibility_invalid_response', requestId, 'accessibility', id, platform);
|
|
647
|
+
}
|
|
648
|
+
if (opts.json)
|
|
649
|
+
console.log(JSON.stringify(snapshot, null, 2));
|
|
650
|
+
else
|
|
651
|
+
printAccessibilitySnapshot(snapshot);
|
|
652
|
+
}
|
|
488
653
|
function simulatorRecordingBase(platform, sessionId) {
|
|
489
654
|
return `/run-cloud/${platform}/${encodeURIComponent(sessionId)}/recordings`;
|
|
490
655
|
}
|
|
@@ -733,6 +898,58 @@ async function createSimulatorSession(platform, opts) {
|
|
|
733
898
|
...(opts.devHost ? { devHost: opts.devHost } : {}),
|
|
734
899
|
});
|
|
735
900
|
}
|
|
901
|
+
async function openSimulatorSessionUrl(platform, sessionId, url) {
|
|
902
|
+
const path = `/run-cloud/${platform}/${encodeURIComponent(sessionId)}/open-url`;
|
|
903
|
+
try {
|
|
904
|
+
const response = await client().post(path, { url });
|
|
905
|
+
const result = typeof response === 'object' && response !== null && !Array.isArray(response)
|
|
906
|
+
? response
|
|
907
|
+
: {};
|
|
908
|
+
if (result.ok !== true
|
|
909
|
+
|| result.platform !== platform
|
|
910
|
+
|| result.sessionId !== sessionId
|
|
911
|
+
|| typeof result.device !== 'string'
|
|
912
|
+
|| result.device.length === 0
|
|
913
|
+
|| typeof result.leaseId !== 'string'
|
|
914
|
+
|| result.leaseId.length === 0
|
|
915
|
+
|| result.url !== url) {
|
|
916
|
+
throw new ApiError(0, 'Simulator open URL returned an invalid acknowledgement', path, {
|
|
917
|
+
code: 'open_url_invalid_response',
|
|
918
|
+
retryable: true,
|
|
919
|
+
sessionId,
|
|
920
|
+
platform,
|
|
921
|
+
action: 'open-url',
|
|
922
|
+
});
|
|
923
|
+
}
|
|
924
|
+
return {
|
|
925
|
+
ok: true,
|
|
926
|
+
platform,
|
|
927
|
+
sessionId,
|
|
928
|
+
device: result.device,
|
|
929
|
+
leaseId: result.leaseId,
|
|
930
|
+
url,
|
|
931
|
+
};
|
|
932
|
+
}
|
|
933
|
+
catch (error) {
|
|
934
|
+
if (error instanceof ApiTransportError) {
|
|
935
|
+
throw new ApiError(0, 'Simulator open URL could not reach the run.cloud API', path, {
|
|
936
|
+
code: 'open_url_transport_error',
|
|
937
|
+
retryable: true,
|
|
938
|
+
sessionId,
|
|
939
|
+
platform,
|
|
940
|
+
action: 'open-url',
|
|
941
|
+
});
|
|
942
|
+
}
|
|
943
|
+
if (error instanceof ApiError) {
|
|
944
|
+
Object.assign(error, {
|
|
945
|
+
sessionId: error.sessionId ?? sessionId,
|
|
946
|
+
platform: error.platform ?? platform,
|
|
947
|
+
action: error.action ?? 'open-url',
|
|
948
|
+
});
|
|
949
|
+
}
|
|
950
|
+
throw error;
|
|
951
|
+
}
|
|
952
|
+
}
|
|
736
953
|
function registerSimulatorCommands(program, platform) {
|
|
737
954
|
const label = platform === 'android' ? 'Android emulator' : 'iOS simulator';
|
|
738
955
|
const article = platform === 'android' ? 'an' : 'an';
|
|
@@ -792,7 +1009,7 @@ function registerSimulatorCommands(program, platform) {
|
|
|
792
1009
|
.requiredOption('--id <id>', 'session id')
|
|
793
1010
|
.option('--json', 'output JSON', false)
|
|
794
1011
|
.action((url, opts) => action(async () => {
|
|
795
|
-
print(await
|
|
1012
|
+
print(await openSimulatorSessionUrl(platform, opts.id, url), opts);
|
|
796
1013
|
}, opts));
|
|
797
1014
|
addInteractionOptions(simulator
|
|
798
1015
|
.command('tap')
|
|
@@ -921,6 +1138,14 @@ function registerSimulatorCommands(program, platform) {
|
|
|
921
1138
|
option: renderDebugOption(rawOption),
|
|
922
1139
|
enabled: booleanArgument(rawEnabled, 'enabled'),
|
|
923
1140
|
}, opts), opts));
|
|
1141
|
+
simulator
|
|
1142
|
+
.command('accessibility-tree')
|
|
1143
|
+
.alias('accessibility')
|
|
1144
|
+
.description(`Read the current accessibility hierarchy from ${article} active ${label} session`)
|
|
1145
|
+
.argument('<id>', 'session id')
|
|
1146
|
+
.option('--timeout <milliseconds>', 'capture timeout in milliseconds (100-60000)', String(DEFAULT_ACCESSIBILITY_TIMEOUT_MS))
|
|
1147
|
+
.option('--json', 'output the stable typed tree as JSON', false)
|
|
1148
|
+
.action((id, opts) => action(() => captureSimulatorAccessibilityTree(platform, id, opts), opts));
|
|
924
1149
|
addInteractionOptions(simulator
|
|
925
1150
|
.command('screenshot')
|
|
926
1151
|
.description(`Capture the current display from ${article} ${label} session`)
|
|
@@ -1036,6 +1261,7 @@ function jsonErrorEnvelope(error) {
|
|
|
1036
1261
|
retryable: error.retryable ?? (error.status === 408 || error.status === 429 || error.status >= 500),
|
|
1037
1262
|
httpStatus: error.status,
|
|
1038
1263
|
...(error.details === undefined ? {} : { details: error.details }),
|
|
1264
|
+
...(error.suggestedAction ? { action: error.suggestedAction } : {}),
|
|
1039
1265
|
},
|
|
1040
1266
|
};
|
|
1041
1267
|
}
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const CLI_VERSION = '0.1.
|
|
1
|
+
export const CLI_VERSION = '0.1.117';
|
package/package.json
CHANGED
|
Binary file
|
|
@@ -1,12 +1,12 @@
|
|
|
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
|
|
3
|
+
description: Operate run.cloud iOS simulator and Android emulator sessions with the CLI or TypeScript SDK. Use for creating, installing, inspecting accessibility or 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
|
|
7
7
|
|
|
8
8
|
Use the `runcloud` CLI for terminal workflows, `@run-cloud/sdk` for TypeScript,
|
|
9
|
-
and `@
|
|
9
|
+
and `@runcloud/ui` for React embeds.
|
|
10
10
|
|
|
11
11
|
## Authenticate
|
|
12
12
|
|
|
@@ -58,6 +58,7 @@ runcloud ios swipe "$SESSION_ID" 0.5 0.8 0.5 0.2 --duration 300 --json
|
|
|
58
58
|
runcloud ios type-text "$SESSION_ID" 'hello' --json
|
|
59
59
|
runcloud ios press-key "$SESSION_ID" enter --json
|
|
60
60
|
runcloud ios press-button "$SESSION_ID" home --json
|
|
61
|
+
runcloud ios accessibility-tree "$SESSION_ID" --json
|
|
61
62
|
runcloud ios screenshot "$SESSION_ID" --output ios.png --json
|
|
62
63
|
```
|
|
63
64
|
|
|
@@ -83,6 +84,15 @@ Android and returns a non-retryable `unsupported_action` on iOS.
|
|
|
83
84
|
An acknowledgement means input dispatch completed. Confirm visible app effects
|
|
84
85
|
with a screenshot or the signed viewer when the outcome matters.
|
|
85
86
|
|
|
87
|
+
## Inspect Accessibility
|
|
88
|
+
|
|
89
|
+
Use `runcloud ios|android accessibility-tree <id> --json` to read the current
|
|
90
|
+
nested role, label, value, state, bounds, identifier, and child hierarchy. Read
|
|
91
|
+
again after a control when the task depends on a state or navigation change.
|
|
92
|
+
Check `truncated` before asserting that an element is absent. Prefer app-owned
|
|
93
|
+
identifiers when available; generated node ids and paths can change with the
|
|
94
|
+
hierarchy. Secure field values are always `null`.
|
|
95
|
+
|
|
86
96
|
## Record the Screen
|
|
87
97
|
|
|
88
98
|
Both platforms expose the same session-scoped recording lifecycle:
|
|
@@ -137,6 +147,7 @@ typed acknowledgements. Use `RunCloudError` fields such as `code`, `retryable`,
|
|
|
137
147
|
|
|
138
148
|
The lifecycle surface also includes `create`, `list`, `get`, `openUrl`, `logs`,
|
|
139
149
|
`followLogs`, and `delete`. Both platforms expose `screenshot`,
|
|
150
|
+
`accessibilityTree`,
|
|
140
151
|
`startRecording`, `listRecordings`, `getRecording`, `stopRecording`, and
|
|
141
152
|
`downloadRecording`; iOS additionally supports `uploadVideo` and
|
|
142
153
|
`uploadMicrophoneAudio`. Inspect the installed types for complete create,
|
|
@@ -170,9 +181,10 @@ unavailable, report that requirement instead of guessing a public URL.
|
|
|
170
181
|
|
|
171
182
|
## Embed a Session
|
|
172
183
|
|
|
173
|
-
- Use `RemoteControl` from `@
|
|
184
|
+
- Use `RemoteControl` from `@runcloud/ui` with the signed session URL.
|
|
174
185
|
- Its ref exposes promise-based `interact` and convenience methods matching the
|
|
175
|
-
SDK controls
|
|
186
|
+
SDK controls, plus `accessibilityTree`. Handle `onInteractionResult` for
|
|
187
|
+
inspectable acknowledgements and `onAccessibilityResult` for tree reads.
|
|
176
188
|
- Add `embed=1` to raw iframe URLs. Add `loadingGuard=1` when interaction should
|
|
177
189
|
wait for streaming and app readiness.
|
|
178
190
|
- Raw iframe requests use `run-cloud:interaction`; acknowledgements use
|
package/runcloud-0.1.115.tgz
DELETED
|
Binary file
|