@revyl/verify 0.3.2 → 0.4.0

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/dist/cli/main.js CHANGED
@@ -1,25 +1,48 @@
1
1
  #!/usr/bin/env node
2
+ import { rm } from 'node:fs/promises';
2
3
  import { createInterface } from 'node:readline/promises';
3
4
  import { parseArgs } from 'node:util';
4
5
  import { RevylClient } from '../api/client.js';
5
- import { missingCredentialsMessage, resolveCredentials } from '../auth.js';
6
+ import { missingCredentialsMessage, resolveCredentials, revylCredentialsPath } from '../auth.js';
6
7
  import { VerifyError, errorMessage, isVerifyError } from '../errors.js';
7
8
  import { PACKAGE_NAME, VERSION } from '../version.js';
8
- import { initProject, validateInitAnswers } from './init.js';
9
+ import { initProject, validateInitAnswers, webAppIdMissingMessage } from './init.js';
9
10
  import { installVerify, packageHasVerify } from './install.js';
10
11
  import { signIn } from './login.js';
12
+ const INIT_USAGE = `npx ${PACKAGE_NAME} init [--platform ios|android|web|mac] [--app-id <id>] [--url <https-url>] [--force] [--yes] [--no-install]`;
13
+ const DOCS = `https://github.com/RevylAI/verify/tree/v${VERSION}/packages/verify/docs`;
11
14
  const HELP = `verify ${VERSION}: open Revyl cloud browsers, devices, and computers from your code
12
15
 
13
16
  Usage
14
- npx ${PACKAGE_NAME} init [--platform web|ios|android|mac] [--app-id <id>] [--url <https-url>] [--force] [--yes] [--no-install]
15
- Install Verify, write verify.config.ts and an example script, then check authentication
17
+ ${INIT_USAGE}
18
+ Install Verify, write verify.config.ts and an example script, and sign in
19
+ npx ${PACKAGE_NAME} login
20
+ Sign in to Revyl through your browser
21
+ npx ${PACKAGE_NAME} logout
22
+ Remove the saved Revyl login from this computer
16
23
  npx ${PACKAGE_NAME} whoami
17
24
  Check which Revyl account your credentials belong to
18
25
 
19
26
  Authentication
20
- REVYL_API_KEY, or a login from the Revyl CLI ('revyl auth login').
27
+ In a terminal, init, login, and your first open() sign you in through your browser and save the
28
+ login to ~/.revyl/credentials.json, which the Revyl CLI shares. In CI, set REVYL_API_KEY instead.
21
29
 
22
- Docs: https://github.com/RevylAI/verify/tree/v${VERSION}/packages/verify/docs
30
+ Docs: ${DOCS}
31
+ `;
32
+ const INIT_HELP = `Usage: ${INIT_USAGE}
33
+
34
+ Writes verify.config.ts and verify/example.mts, installs Verify when the project lacks it, and signs
35
+ you in through your browser when no credentials are saved.
36
+
37
+ --platform ios, android, web, or mac. Without it, init asks. iOS and Android without --app-id
38
+ open the device's built-in Settings app, so the example runs before you upload anything.
39
+ --app-id The Revyl app to open. Required for web; create a Web app at https://app.revyl.ai first.
40
+ --url The public HTTPS URL a web target opens (default https://example.com).
41
+ --force Overwrite existing files.
42
+ --yes Install Verify without asking.
43
+ --no-install Skip installing Verify.
44
+
45
+ Docs: ${DOCS}/getting-started.md
23
46
  `;
24
47
  const USAGE_CODES = new Set(['INVALID_ARGUMENT', 'AUTH_MISSING', 'AUTH_REJECTED']);
25
48
  export async function main(argv) {
@@ -37,8 +60,18 @@ export async function main(argv) {
37
60
  return 0;
38
61
  case 'init':
39
62
  return initCommand(rest);
40
- case 'whoami':
41
- return whoamiCommand();
63
+ case 'login':
64
+ case 'logout':
65
+ case 'whoami': {
66
+ const { values } = parseArgs({ args: rest, options: { help: { type: 'boolean', short: 'h' } } });
67
+ if (values.help) {
68
+ process.stdout.write(HELP);
69
+ return 0;
70
+ }
71
+ if (command === 'login')
72
+ return signInAndCheck(Boolean(process.stdin.isTTY) && !process.env.CI);
73
+ return command === 'logout' ? logoutCommand() : whoamiCommand();
74
+ }
42
75
  default:
43
76
  process.stderr.write(`Unknown command: ${command}\n\n${HELP}`);
44
77
  return 2;
@@ -54,8 +87,13 @@ async function initCommand(args) {
54
87
  force: { type: 'boolean' },
55
88
  yes: { type: 'boolean' },
56
89
  'no-install': { type: 'boolean' },
90
+ help: { type: 'boolean', short: 'h' },
57
91
  },
58
92
  });
93
+ if (values.help) {
94
+ process.stdout.write(INIT_HELP);
95
+ return 0;
96
+ }
59
97
  const interactive = Boolean(process.stdin.isTTY) && !process.env.CI;
60
98
  let answers;
61
99
  if (values.platform) {
@@ -86,44 +124,46 @@ async function initCommand(args) {
86
124
  answers ??= await askInitQuestions(values['app-id'], values.url);
87
125
  const result = await initProject(process.cwd(), answers, { force: values.force ?? false });
88
126
  process.stdout.write(`Created ${result.created.join(' and ')}${result.gitignoreUpdated ? ', and added .verify/ to .gitignore' : ''}.\n`);
127
+ await signInAndCheck(interactive);
128
+ const steps = answers.platform === 'mac'
129
+ ? ['Mac computers are early access. Ask support@revyl.ai about access.', 'Once your organization has Mac access, run: node verify/example.mts']
130
+ : answers.appId
131
+ ? ['Run: node verify/example.mts']
132
+ : ['Run: node verify/example.mts', 'To test your own app, put its Revyl app id in verify.config.ts in place of the Settings bundle id.'];
133
+ process.stdout.write([
134
+ 'Next:',
135
+ ...steps.map((step, index) => ` ${index + 1}. ${step}`),
136
+ '',
137
+ ].join('\n'));
138
+ return 0;
139
+ }
140
+ async function signInAndCheck(interactive) {
89
141
  const credentials = await resolveCredentials();
90
142
  if (!credentials) {
91
- if (interactive) {
92
- await signIn();
93
- }
94
- else {
143
+ if (!interactive)
95
144
  throw new VerifyError('AUTH_MISSING', `${missingCredentialsMessage()} In non-interactive environments, set the REVYL_API_KEY secret.`);
96
- }
145
+ await signIn();
97
146
  }
98
147
  try {
99
- await whoamiCommand();
148
+ return await whoamiCommand();
100
149
  }
101
150
  catch (error) {
102
151
  if (!interactive || !isVerifyError(error) || error.code !== 'AUTH_REJECTED')
103
152
  throw error;
104
153
  await signIn({ force: true });
105
- await whoamiCommand();
154
+ return whoamiCommand();
106
155
  }
107
- const steps = [
108
- 'Configure the generated verify.config.ts if needed',
109
- ...(answers.platform === 'mac' ? ['Mac computers are early access. Ask support@revyl.ai about access.'] : []),
110
- ...(answers.platform === 'mac' || answers.appId ? [] : ['Put your Revyl app id in verify.config.ts (or set REVYL_APP_ID)']),
111
- 'node verify/example.ts',
112
- ];
113
- process.stdout.write([
114
- 'Next:',
115
- ...steps.map((step, index) => ` ${index + 1}. ${step}`),
116
- '',
117
- ].join('\n'));
118
- return 0;
119
156
  }
120
157
  async function askInitQuestions(appId, url) {
121
158
  const readline = createInterface({ input: process.stdin, output: process.stdout });
122
159
  try {
123
- const platform = ((await readline.question('What do you want to open? [web/ios/android/mac] (web): ')).trim().toLowerCase() || 'web');
160
+ const platform = ((await readline.question('What do you want to open? [ios/android/web/mac] (ios): ')).trim().toLowerCase() || 'ios');
124
161
  if (platform === 'mac')
125
162
  return validateInitAnswers({ platform, ...(appId ? { appId } : {}), ...(url ? { url } : {}) });
126
- const id = appId ?? ((await readline.question('Revyl app id (Enter to fill in later): ')).trim() || undefined);
163
+ const question = platform === 'web' ? 'Revyl Web app id (Enter if you have none yet): ' : 'Revyl app id (Enter to use the built-in Settings app): ';
164
+ const id = appId ?? ((await readline.question(question)).trim() || undefined);
165
+ if (platform === 'web' && !id)
166
+ throw new VerifyError('INVALID_ARGUMENT', webAppIdMissingMessage(url));
127
167
  const webUrl = platform === 'web' ? (url ?? ((await readline.question('HTTPS URL to open (Enter for https://example.com): ')).trim() || undefined)) : undefined;
128
168
  return validateInitAnswers({ platform, ...(id ? { appId: id } : {}), ...(webUrl ? { url: webUrl } : {}) });
129
169
  }
@@ -141,6 +181,21 @@ async function whoamiCommand() {
141
181
  process.stdout.write(`Signed in to Revyl as ${name}${org ? ` in ${org}` : ''} (credentials from ${credentials.source}).\n`);
142
182
  return 0;
143
183
  }
184
+ async function logoutCommand() {
185
+ try {
186
+ await rm(revylCredentialsPath());
187
+ }
188
+ catch (error) {
189
+ if (error.code !== 'ENOENT')
190
+ throw new VerifyError('AUTH_MISSING', `Could not remove ~/.revyl/credentials.json: ${errorMessage(error)}`);
191
+ process.stdout.write('No saved Revyl login on this computer.\n');
192
+ return 0;
193
+ }
194
+ process.stdout.write('Signed out: removed ~/.revyl/credentials.json, which the Revyl CLI shares. The API key it held still works until you delete it in Revyl (Settings → API keys).\n');
195
+ if (process.env.REVYL_API_KEY?.trim())
196
+ process.stdout.write('REVYL_API_KEY is still set in this environment, so Verify keeps using it.\n');
197
+ return 0;
198
+ }
144
199
  export async function runCli(argv) {
145
200
  const [major = 0, minor = 0] = process.versions.node.split('.').map(Number);
146
201
  if (major < 22 || (major === 22 && minor < 18)) {
@@ -27,6 +27,15 @@ export declare class ComputerSession implements Session {
27
27
  open(target: HostedTargetConfig): Promise<OpenedSession>;
28
28
  report(): Promise<Record<string, unknown>>;
29
29
  navigate(): Promise<never>;
30
+ tap(): Promise<never>;
31
+ type(): Promise<never>;
32
+ swipe(): Promise<never>;
33
+ pressKey(): Promise<never>;
34
+ back(): Promise<never>;
35
+ openUrl(): Promise<never>;
36
+ launchApp(): Promise<never>;
37
+ locate(): Promise<never>;
38
+ screenSize(): Promise<never>;
30
39
  get expiresAt(): Date | undefined;
31
40
  recording(): Promise<ComputerRecording | null>;
32
41
  instruction(): Promise<never>;
@@ -123,6 +123,33 @@ export class ComputerSession {
123
123
  navigate() {
124
124
  return this.unavailable('navigate');
125
125
  }
126
+ tap() {
127
+ return this.unavailable('tap');
128
+ }
129
+ type() {
130
+ return this.unavailable('type');
131
+ }
132
+ swipe() {
133
+ return this.unavailable('swipe');
134
+ }
135
+ pressKey() {
136
+ return this.unavailable('pressKey');
137
+ }
138
+ back() {
139
+ return this.unavailable('back');
140
+ }
141
+ openUrl() {
142
+ return this.unavailable('openUrl');
143
+ }
144
+ launchApp() {
145
+ return this.unavailable('launchApp');
146
+ }
147
+ locate() {
148
+ return this.unavailable('locate');
149
+ }
150
+ screenSize() {
151
+ return this.unavailable('screenSize');
152
+ }
126
153
  get expiresAt() {
127
154
  return this.expiresAtMs === undefined ? undefined : new Date(this.expiresAtMs);
128
155
  }
package/dist/errors.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- export type VerifyErrorCode = 'INVALID_ARGUMENT' | 'CONFIG_NOT_FOUND' | 'CONFIG_INVALID' | 'AUTH_MISSING' | 'COMPUTER_ID_MISSING' | 'AUTH_REJECTED' | 'EDGE_BLOCKED' | 'API_ERROR' | 'SESSION_START_FAILED' | 'SESSION_ENDED' | 'SESSION_BUSY' | 'COMPUTER_LIMIT' | 'NOT_AVAILABLE_ON_PLATFORM' | 'ACTION_FAILED' | 'ACTION_UNCERTAIN' | 'INSTRUCTION_FAILED' | 'VALIDATION_FAILED' | 'STEP_FAILED' | 'COMMAND_FAILED' | 'NOT_FOUND' | 'ACCESS_DENIED' | 'TIMEOUT';
2
- export type RefusalReason = 'web_runtime_unavailable' | 'web_runtime_not_configured' | 'computer_limit' | 'runtime_unavailable' | 'computer_runtime_unavailable' | 'computer_at_capacity' | 'computer_not_running' | 'computer_cannot_host_platform' | 'computer_not_found' | 'web_build_required' | 'web_build_not_supported' | 'ios_build_required' | 'ios_build_not_supported' | 'android_build_required' | 'android_build_not_supported' | 'web_session_conflict' | 'ios_session_conflict' | 'android_session_conflict' | 'not_available_on_platform' | 'request_timeout' | 'protocol_error';
1
+ export type VerifyErrorCode = 'INVALID_ARGUMENT' | 'CONFIG_NOT_FOUND' | 'CONFIG_INVALID' | 'AUTH_MISSING' | 'COMPUTER_ID_MISSING' | 'AUTH_REJECTED' | 'EDGE_BLOCKED' | 'API_ERROR' | 'SESSION_START_FAILED' | 'SESSION_ENDED' | 'SESSION_BUSY' | 'COMPUTER_LIMIT' | 'NOT_AVAILABLE_ON_PLATFORM' | 'ACTION_FAILED' | 'ACTION_UNCERTAIN' | 'INSTRUCTION_FAILED' | 'VALIDATION_FAILED' | 'STEP_FAILED' | 'COMMAND_FAILED' | 'NOT_FOUND' | 'ACCESS_DENIED' | 'MODEL_ERROR' | 'MODEL_KEY_MISSING' | 'TIMEOUT';
2
+ export type RefusalReason = 'web_runtime_unavailable' | 'web_runtime_not_configured' | 'computer_limit' | 'runtime_unavailable' | 'computer_runtime_unavailable' | 'computer_at_capacity' | 'computer_platform_at_capacity' | 'computer_not_running' | 'computer_cannot_host_platform' | 'computer_not_found' | 'web_build_required' | 'web_build_not_supported' | 'ios_build_required' | 'ios_build_not_supported' | 'android_build_required' | 'android_build_not_supported' | 'web_session_conflict' | 'ios_session_conflict' | 'android_session_conflict' | 'not_available_on_platform' | 'request_timeout' | 'protocol_error';
3
3
  export declare class VerifyError extends Error {
4
4
  readonly code: VerifyErrorCode;
5
5
  readonly reason?: RefusalReason;
package/dist/index.d.ts CHANGED
@@ -6,3 +6,8 @@ export type { HostedTargetConfig, MobileAppConfig, MobileTargetConfig, ComputerT
6
6
  export type { Platform, ComputerPlatform, MobilePlatform } from './api/types.js';
7
7
  export { VerifyError, isVerifyError } from './errors.js';
8
8
  export type { RefusalReason, VerifyErrorCode } from './errors.js';
9
+ export { localAgent } from './agent/loop.js';
10
+ export type { Grounding, LocalAgent, LocalAgentOptions, Location, ModelRun, ModelTurn, Point, SwipeDirection, Target } from './agent/loop.js';
11
+ export type { CustomModel, ModelOption } from './agent/providers.js';
12
+ export { anthropic, openaiCompatible } from './agent/model.js';
13
+ export type { AnthropicOptions, JsonSchema, ModelAdapter, ModelReply, ModelRequest, ModelTool, ModelToolCall, ModelUsage, OpenAICompatibleOptions } from './agent/model.js';
package/dist/index.js CHANGED
@@ -1,3 +1,5 @@
1
1
  export { attach, currentComputer, open } from './open.js';
2
2
  export { defineConfig } from './config.js';
3
3
  export { VerifyError, isVerifyError } from './errors.js';
4
+ export { localAgent } from './agent/loop.js';
5
+ export { anthropic, openaiCompatible } from './agent/model.js';
package/dist/open.d.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import { type TargetConfig } from './config.js';
2
2
  import { type OpenedSession } from './session.js';
3
3
  import { ComputerSession } from './computer-session.js';
4
+ import type { LocalAgent } from './agent/loop.js';
5
+ import { type ModelOption } from './agent/providers.js';
4
6
  export interface OpenOptions {
5
7
  /** The config file to read target names from. Defaults to the nearest verify.config.ts at or above the current folder. */
6
8
  config?: string;
@@ -8,6 +10,10 @@ export interface OpenOptions {
8
10
  log?: false | ((line: string) => void);
9
11
  backendUrl?: string;
10
12
  appUrl?: string;
13
+ /** Runs every instruction and validation on your model, such as 'anthropic/claude-sonnet-5-5', instead of Revyl's agent. Browsers and devices only. */
14
+ model?: ModelOption;
15
+ /** Runs every instruction and validation on a custom agent from localAgent(). Pass model or agent, not both. */
16
+ agent?: LocalAgent;
11
17
  }
12
18
  export interface AttachOptions extends Pick<OpenOptions, 'log' | 'backendUrl' | 'appUrl'> {
13
19
  stopOnClose?: boolean;
package/dist/open.js CHANGED
@@ -5,6 +5,7 @@ import { DEFAULT_STEP_TIMEOUT_MS, loadConfig, validateTarget } from './config.js
5
5
  import { VerifyError } from './errors.js';
6
6
  import { DeviceSession } from './session.js';
7
7
  import { ComputerSession } from './computer-session.js';
8
+ import { chooseAgent } from './agent/providers.js';
8
9
  const signIns = new Map();
9
10
  async function credentialsForUse(options) {
10
11
  const credentials = await resolveCredentials();
@@ -53,10 +54,14 @@ export async function open(target, options = {}) {
53
54
  else {
54
55
  resolved = validateTarget(target);
55
56
  }
57
+ const agent = chooseAgent(options, 'open()');
58
+ if (agent && (resolved.platform === 'mac' || resolved.platform === 'linux')) {
59
+ throw new VerifyError('NOT_AVAILABLE_ON_PLATFORM', 'Your own model runs steps on browsers and devices, not on computers.');
60
+ }
56
61
  const credentials = await credentialsForUse(options);
57
62
  const log = options.log === false ? undefined : (options.log ?? ((line) => void process.stderr.write(`[verify] ${line}\n`)));
58
63
  const client = new RevylClient({ token: credentials.token, backendUrl: options.backendUrl });
59
64
  if (resolved.platform === 'mac' || resolved.platform === 'linux')
60
65
  return ComputerSession.start(client, resolved, { stepTimeout, log, appUrl: options.appUrl });
61
- return DeviceSession.start(client, resolved, { stepTimeout, log, appUrl: options.appUrl });
66
+ return DeviceSession.start(client, resolved, { stepTimeout, log, appUrl: options.appUrl, ...(agent ? { agent } : {}) });
62
67
  }
package/dist/session.d.ts CHANGED
@@ -1,19 +1,27 @@
1
1
  import { RevylClient } from './api/client.js';
2
2
  import type { LiveStepType, Platform } from './api/types.js';
3
3
  import type { HostedTargetConfig, ResolvedDeviceTarget } from './config.js';
4
+ import { type LocalAgent, type Location, type ModelRun, type Point, type SwipeDirection } from './agent/loop.js';
5
+ import { type ModelOption } from './agent/providers.js';
4
6
  export type StepType = LiveStepType;
5
7
  /** The outcome of one instruction or validation, as Revyl's agent reported it. */
6
8
  export interface StepResult {
7
9
  type: StepType;
8
10
  text: string;
9
11
  passed: boolean;
10
- /** Revyl's explanation, when it gave one. */
12
+ /** Revyl's explanation, or your model's when the step ran on a local agent. */
11
13
  reason?: string;
12
14
  durationMs: number;
15
+ /** Present when the step ran on your model: its name, grounding, turns and token use. */
16
+ model?: ModelRun;
13
17
  }
14
18
  export interface StepOptions {
15
- /** Milliseconds before the step is cancelled on Revyl. Defaults to stepTimeout from the config, or 180000. */
19
+ /** Milliseconds before the step is cancelled. Defaults to stepTimeout from the config, or 180000. */
16
20
  timeout?: number;
21
+ /** Runs this step on your model, such as 'anthropic/claude-sonnet-5-5', instead of Revyl's agent. Overrides the model passed to open(). */
22
+ model?: ModelOption;
23
+ /** Runs this step on a custom agent from localAgent(). Overrides the one passed to open(); pass model or agent, not both. */
24
+ agent?: LocalAgent;
17
25
  }
18
26
  export interface ValidationOptions extends StepOptions {
19
27
  /** Return a failed result instead of throwing VALIDATION_FAILED. */
@@ -95,11 +103,37 @@ export interface OpenedSession extends Session {
95
103
  report(): Promise<Record<string, unknown>>;
96
104
  navigate(url: string): Promise<void>;
97
105
  open(target: HostedTargetConfig): Promise<OpenedSession>;
106
+ /** Taps a point in device units, or an element Revyl locates from a description. Returns the point tapped. Browsers and devices only. */
107
+ tap(at: Location): Promise<Point>;
108
+ /** Taps a text field and types into it, clearing it first unless clear is false. Returns the point tapped. Browsers and devices only. */
109
+ type(at: Location, text: string, options?: {
110
+ clear?: boolean;
111
+ }): Promise<Point>;
112
+ /** Swipes from a point or the screen's center. 'up' moves the finger up, which scrolls content down. Browsers and devices only. */
113
+ swipe(direction: SwipeDirection, options?: {
114
+ from?: Location;
115
+ }): Promise<void>;
116
+ /** Presses Enter or Backspace on the focused field. Browsers and devices only. */
117
+ pressKey(key: 'enter' | 'backspace'): Promise<void>;
118
+ /** Goes back: browser history on Web, the back button on Android. Not on iOS. */
119
+ back(): Promise<void>;
120
+ /** Opens a URL or deep link on a device, or loads a URL in a browser. */
121
+ openUrl(url: string): Promise<void>;
122
+ /** Launches an installed app by bundle ID or package name. Devices only. */
123
+ launchApp(bundleId: string): Promise<void>;
124
+ /** Asks Revyl's grounding model where an element is on the current screen, in device units. Browsers and devices only. */
125
+ locate(target: string): Promise<Point>;
126
+ /** The screen size in device units: iOS points, Android and Web pixels. Browsers and devices only. */
127
+ screenSize(): Promise<{
128
+ width: number;
129
+ height: number;
130
+ }>;
98
131
  }
99
132
  export interface SessionOptions {
100
133
  stepTimeout: number;
101
134
  log: ((line: string) => void) | undefined;
102
135
  appUrl?: string;
136
+ agent?: LocalAgent;
103
137
  }
104
138
  export interface HostingParent {
105
139
  id: string;
@@ -125,6 +159,8 @@ export declare class DeviceSession implements Session {
125
159
  private stepRunning;
126
160
  private parent?;
127
161
  private readySignal?;
162
+ private size?;
163
+ private attribution?;
128
164
  get expiresAt(): Date | undefined;
129
165
  private constructor();
130
166
  static start(client: RevylClient, target: ResolvedDeviceTarget, options: SessionOptions, parent?: HostingParent): Promise<DeviceSession>;
@@ -143,6 +179,7 @@ export declare class DeviceSession implements Session {
143
179
  }): Promise<Uint8Array>;
144
180
  report(): Promise<Record<string, unknown>>;
145
181
  navigate(url: string): Promise<void>;
182
+ private navigateTo;
146
183
  exec(): Promise<ExecResult>;
147
184
  open(): Promise<never>;
148
185
  recording(): Promise<never>;
@@ -156,6 +193,38 @@ export declare class DeviceSession implements Session {
156
193
  [Symbol.asyncDispose](): Promise<void>;
157
194
  private step;
158
195
  private runStep;
196
+ private runLocal;
197
+ private agentControls;
198
+ /** Taps a point in device units, or an element Revyl locates from a description. Returns the point tapped. */
199
+ tap(at: Location): Promise<Point>;
200
+ /** Taps a text field and types into it, clearing it first unless clear is false. Returns the point tapped. */
201
+ type(at: Location, text: string, options?: {
202
+ clear?: boolean;
203
+ }): Promise<Point>;
204
+ /** Swipes from a point or the screen's center. 'up' moves the finger up, which scrolls content down. */
205
+ swipe(direction: SwipeDirection, options?: {
206
+ from?: Location;
207
+ }): Promise<void>;
208
+ /** Presses Enter or Backspace on the focused field. */
209
+ pressKey(key: 'enter' | 'backspace'): Promise<void>;
210
+ /** Goes back: browser history on Web, the back button on Android. Not on iOS. */
211
+ back(): Promise<void>;
212
+ /** Opens a URL or deep link on a device, or loads a URL in a browser. */
213
+ openUrl(url: string): Promise<void>;
214
+ /** Launches an installed app by bundle ID or package name. Devices only. */
215
+ launchApp(bundleId: string): Promise<void>;
216
+ /** Asks Revyl's grounding model where an element is on the current screen, in device units. */
217
+ locate(target: string): Promise<Point>;
218
+ /** The screen size in device units: iOS points, Android and Web pixels. */
219
+ screenSize(): Promise<{
220
+ width: number;
221
+ height: number;
222
+ }>;
223
+ private readScreenSize;
224
+ private capture;
225
+ private perform;
226
+ private resolve;
227
+ private act;
159
228
  private launch;
160
229
  private waitForScreenContent;
161
230
  private log;