@revyl/verify 0.5.0 → 0.6.1

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/CHANGELOG.md CHANGED
@@ -1,6 +1,56 @@
1
1
  # Changelog
2
2
 
3
- ## 0.5.0 (unreleased)
3
+ ## 0.6.1 (unreleased)
4
+
5
+ - An iOS or Android `open()` that Revyl refuses as invalid (HTTP 400 or 422) throws `INVALID_ARGUMENT` with Revyl's explanation instead of a bare `API_ERROR … failed with HTTP 422`. For a device model or OS version Revyl doesn't offer, the message lists the values it accepts, as in `Revyl refused to start the session (HTTP 422): Unsupported ios runtime '18.5'. Available: ['iOS 27.1', …, 'iOS 18.5']. Change the target to a value Revyl accepts and run again.` No session was started. Web and hosted starts keep their safe diagnostic codes and copy no server text.
6
+ - `device.osVersion` is the name Revyl uses, such as `'iOS 18.5'` or `'Android 14'`, as before; Verify sends it unchanged. The docs now show it that way, with models Revyl offers (`iPhone 16`, `iPhone Air`, `Pixel 7`).
7
+ - `verify init --platform web --url <url>` with a URL other than `https://example.com` writes an example with one validation that passes on any page that loads, instead of example.com's steps.
8
+ - Mac computer messages say what Revyl checks: a current paid plan. `init --platform mac` prints that instead of "early access", and computer errors say `Mac computers need a current paid Revyl plan, and Linux computers are not generally available.`
9
+ - Every other call sends the same requests as 0.6.0.
10
+ - Docs: device selectors Revyl accepts, a mobile exact-actions script that searches Settings for Accessibility, a web exact-actions script that scrolls back up before it locates the cart, `node verify/checkout.mts` in CI, non-owning attach examples that navigate to a public page, Mac availability on paid plans, `init --platform` in the README, your own model's Claude results, and `end-to-end.md` marked as a record of the 0.1.0 and 0.2.0 runs.
11
+
12
+ ## Go 0.5.1 (unreleased)
13
+
14
+ Published as the `go/v0.5.1` tag.
15
+
16
+ - `ErrValidationFailed` names the step and the reason, as TypeScript does: `Validation failed: "<text>". <reason> See <link>`.
17
+ - An iOS or Android `Open` that Revyl refuses as invalid (HTTP 400 or 422) returns `ErrInvalidArgument` with Revyl's explanation, as in TypeScript 0.6.1, including the device models or OS versions it accepts. No session was started.
18
+ - The computer availability message says Mac computers need a current paid Revyl plan. Every other call sends the same requests as v0.5.0.
19
+
20
+ ## Python 0.4.1 (unreleased)
21
+
22
+ - `VALIDATION_FAILED` names the step and the reason, as TypeScript does: `Validation failed: "<text>". <reason> See <link>`. `AsyncSession` gets the same message.
23
+ - An iOS or Android `open()` that Revyl refuses as invalid (HTTP 400 or 422) raises `INVALID_ARGUMENT` with Revyl's explanation, as in TypeScript 0.6.1, including the device models or OS versions it accepts. No session was started.
24
+ - The computer availability message says Mac computers need a current paid Revyl plan. Every other call sends the same requests as 0.4.0.
25
+ - Docs: device selectors Revyl accepts, `open_url("https://example.com")` in the exact-actions example, and a non-owning attach example.
26
+
27
+ ## 0.6.0 (2026-11-12)
28
+
29
+ - Early access: a Mac computer's screen. `tap({ x, y })` clicks a point in screenshot pixels and resolves with it, `screenSize()` returns the screenshot's size, `typeFocused(text, { clear: false })` types where the cursor is, and `pressKey(key, { modifiers })` takes the Web keys and modifiers, where `'meta'` is Command and `'alt'` is Option, as in `pressKey('a', { modifiers: ['meta'] })` to select all.
30
+ - `typeFocused` on a computer requires `{ clear: false }` and throws `INVALID_ARGUMENT` without it, naming the fix. It takes up to 16,384 characters, with line breaks and tabs; other control characters are refused before anything is sent. Text over 500 characters goes in pieces, in order, and a failure part way says how many characters were certainly typed and whether the failed piece may have been.
31
+ - A point past the screen's edge throws `INVALID_ARGUMENT` naming the valid range, and nothing is clicked; negative and fractional points are refused before anything is sent. There is no extra request per click.
32
+ - Screen errors: someone controlling the screen in the Revyl viewer (`screen_control_held`) throws `SESSION_BUSY` and nothing happened; an action Revyl can't confirm (`screen_input_uncertain`, `screen_unavailable`, `screen_input_unavailable`) throws `ACTION_UNCERTAIN`; a 403 throws `AUTH_REJECTED` saying the key's owner needs the Member role or above and CI runner keys can't control the screen yet; a 503 `authentication_unavailable` throws `API_ERROR` and is safe to retry. These are read from the answer's `detail`, so `screen_unavailable` is uncertain as a 409 or a 503. Verify never repeats a screen action, except `screen_starting` (409, nothing happened): it waits as `Retry-After` asks and tries again for up to 30 seconds, then throws `SESSION_BUSY` naming when to try again.
33
+ - `tap({ target })` on a Mac computer: Revyl finds the described element on a fresh screenshot, clicks it, and `tap` resolves with the point it clicked. It takes several seconds and can click a look-alike, or something else when the target isn't on the screen, so the docs say to check the point or a screenshot afterwards. `screen_target_not_found` throws `ACTION_FAILED` and `screen_target_unavailable` throws `API_ERROR`, both with nothing clicked; a backend without described clicks (422 `invalid_request`) throws `NOT_AVAILABLE_ON_PLATFORM` saying to pass a point.
34
+ - `locate()` and the other exact actions aren't available on computers. They throw `NOT_AVAILABLE_ON_PLATFORM` before anything is sent, naming `tap({ x, y })`, `tap({ target })`, `typeFocused`, `pressKey` and `exec()`.
35
+ - Every other call sends the same requests as 0.5.0.
36
+ - Error messages on a computer say what happened and what to do next. `screen_control_lost` on a screen action throws `SESSION_BUSY` saying nothing was sent and to try again. `owner_unavailable`, a 5xx or no answer on a screen action throws `ACTION_UNCERTAIN` saying to take a screenshot before repeating it. `invalid_request` on `tap({ x, y })` says nothing was clicked and to pick the point from a new screenshot, and `screen_key_unsupported` says nothing was pressed. Partial typing counts characters as Unicode code points, the same in TypeScript, Python and Go, and a failure in the first piece says nothing was certainly typed. Every other computer refusal adds a next step, and the `screen_starting` message says "1 second" rather than "1 seconds".
37
+ - Docs: a Screen section in `computers.md`, the platform table, `api.md`, `errors.md`, `agents.md` and `llms.txt`. The Web docs say `'meta'` is not the shortcut key there; use `'ctrl'`.
38
+
39
+ ## Go 0.5.0 (2026-11-12)
40
+
41
+ Published as the `go/v0.5.0` tag.
42
+
43
+ - Early access: a Mac computer's screen, as in TypeScript 0.6.0. `Tap(ctx, verify.Point{X, Y})`, `Tap(ctx, verify.Element("description"))` returning the point it clicked, `ScreenSize(ctx)`, `TypeFocused(ctx, text, TypeOptions{Clear})` with `Clear` pointing to `false`, and `PressKey(ctx, key, KeyOptions{Modifiers})`, where `ModifierMeta` is Command. Leaving `Clear` unset returns `ErrInvalidArgument` naming the fix.
44
+ - The same limits, errors and refusals as TypeScript 0.6.0: `ErrSessionBusy` for `screen_control_held`, `ErrActionUncertain` for an action Revyl can't confirm, `ErrAuthRejected` for a 403, `ErrActionFailed` for `screen_target_not_found`, a wait of up to 30 seconds on `screen_starting`, and `ErrNotAvailableOnPlatform` before any request for `Locate` and the other exact actions. Every other call sends the same requests as v0.4.0.
45
+ - The same error messages as TypeScript 0.6.0, each with a next step, with partial typing counted in Unicode code points.
46
+
47
+ ## Python 0.4.0 (2026-11-12)
48
+
49
+ - Early access: a Mac computer's screen, as in TypeScript 0.6.0. `tap(Point(x, y))`, `tap("description")` returning the point it clicked, `screen_size()`, `type_focused(text, clear=False)` and `press_key(key, modifiers=[...])`, where `"meta"` is Command. Leaving `clear` out raises `INVALID_ARGUMENT` naming the fix. `AsyncSession` has the same methods.
50
+ - The same limits, errors and refusals as TypeScript 0.6.0, and every other call sends the same requests as 0.3.0.
51
+ - The same error messages as TypeScript 0.6.0, each with a next step, with partial typing counted in Unicode code points.
52
+
53
+ ## 0.5.0 (2026-11-12)
4
54
 
5
55
  - Early access: more exact actions on Web, iOS and Android. `doubleTap(at)` double-taps (double-clicks on Web), `longPress(at, { duration })` presses and holds for `duration` milliseconds (1 to 5000, 1500 by default), and `drag(from, to)` presses at one location, moves to the other and releases. They take a description or screenshot pixels like `tap`, and `doubleTap` and `longPress` resolve with the point.
6
56
  - Web: `typeFocused(text, { clear })` types into the field that already has focus, without clicking, and replaces its text unless `clear` is `false`, the same default as `type()`. `pressKey` also takes `'tab'`, `'escape'`, `'delete'`, `'space'`, `'arrowup'`, `'arrowdown'`, `'arrowleft'`, `'arrowright'`, `'home'`, `'end'`, `'pageup'` and `'pagedown'`, and `{ modifiers }`: up to four of `'ctrl'`, `'shift'`, `'alt'` and `'meta'`, with which a lowercase letter or digit works too, as in `pressKey('a', { modifiers: ['ctrl'] })`. The `Key` and `KeyModifier` types are exported.
@@ -10,7 +60,7 @@
10
60
  - Every existing call sends the same requests as 0.4.1. Your own model choosing `open_url` with a deep link on Web gets the `openUrl()` wording.
11
61
  - Docs: the platform table, `api.md`, the web and mobile guides with a script for the new calls, `agents.md` and `llms.txt`.
12
62
 
13
- ## Go 0.4.0 (unreleased)
63
+ ## Go 0.4.0 (2026-11-12)
14
64
 
15
65
  Published as the `go/v0.4.0` tag.
16
66
 
@@ -20,7 +70,7 @@ Published as the `go/v0.4.0` tag.
20
70
  - A call the platform lacks returns `ErrNotAvailableOnPlatform` before anything is sent, naming the alternative. Your own model can choose the new actions with both groundings, so it sees more tools; the tools it had send the same requests as v0.3.1, as does every existing call.
21
71
  - `ErrInstructionFailed` names the step and the reason, as TypeScript does: `Instruction failed: "<text>". <reason> See <link>`. The uncertain-action message says "The step deadline passed". Your own model choosing `open_url` with a deep link on Web gets the `OpenURL` wording.
22
72
 
23
- ## Python 0.3.0 (unreleased)
73
+ ## Python 0.3.0 (2026-11-12)
24
74
 
25
75
  - Early access: more exact actions on Web, iOS and Android, as in TypeScript 0.5.0. `double_tap(at)` and `long_press(at, duration=None)` return the point; `duration` is seconds, 0.001 to 5 in whole milliseconds, 1.5 by default. `drag(start, end)` presses, moves and releases.
26
76
  - Web: `type_focused(text, clear=True)` types into the field that already has focus and replaces its text unless `clear` is `False`. `press_key(key, modifiers=())` adds `"tab"`, `"escape"`, `"delete"`, `"space"`, the arrows, `"home"`, `"end"`, `"pageup"` and `"pagedown"`, and modifiers (`"ctrl"`, `"shift"`, `"alt"`, `"meta"`), with which a lowercase letter or digit works too. `Key` and `KeyModifier` are exported.
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # Verify
2
2
 
3
- ![Terminal output from one Verify script beside a Mac computer's Terminal window showing the macOS version, and the screens the script reached in a cloud browser, an Android emulator, and an iOS simulator](https://raw.githubusercontent.com/RevylAI/verify/v0.5.0/docs/images/verify-hero.webp)
3
+ ![Terminal output from one Verify script beside a Mac computer's Terminal window showing the macOS version, and the screens the script reached in a cloud browser, an Android emulator, and an iOS simulator](https://raw.githubusercontent.com/RevylAI/verify/v0.6.1/docs/images/verify-hero.webp)
4
4
 
5
- Verify runs end-to-end checks in a cloud browser, iOS simulator, or Android emulator on Revyl. You write what the agent should do and what must be true afterward, in plain sentences, and call it from a script, a coding agent, CI, or the test runner you already use. When you know exactly what to press, you can tap, type and swipe yourself instead. Verify also opens Mac computers for commands, files, and screenshots.
5
+ Verify runs end-to-end checks in a cloud browser, iOS simulator, or Android emulator on Revyl. You write what the agent should do and what must be true afterward, in plain sentences, and call it from a script, a coding agent, CI, or the test runner you already use. When you know exactly what to press, you can tap, type and swipe yourself instead. Verify also opens Mac computers for commands, files, screenshots, and clicks and keys on the screen.
6
6
 
7
7
  ```ts
8
8
  import { open } from '@revyl/verify';
@@ -38,12 +38,12 @@ Ask me before anything that needs my login or approval.
38
38
  Or set it up yourself:
39
39
 
40
40
  ```bash
41
- npx @revyl/verify init
41
+ npx @revyl/verify init --platform ios
42
42
  ```
43
43
 
44
- `init` adds Verify to your project, asks what to open, writes `verify.config.ts` and an example script, signs you in through the browser on the first interactive run, and prints the command that runs the example. With no app id, the iOS and Android examples open the built-in Settings app, so they run on a new account before you upload anything. To skip the config, run `npm install @revyl/verify` and save the example above as `verify/check.mts`, then run `node verify/check.mts`. [Getting started](https://github.com/RevylAI/verify/blob/v0.5.0/packages/verify/docs/getting-started.md) covers sign-in, your own app, and [CI](https://github.com/RevylAI/verify/blob/v0.5.0/packages/verify/docs/ci.md).
44
+ `init` adds Verify to your project, writes `verify.config.ts` and an example script for the platform you pass (`web`, `ios`, `android` or `mac`; in a terminal, leave `--platform` out and it asks), writes `verify.config.ts` and an example script, signs you in through the browser on the first interactive run, and prints the command that runs the example. With no app id, the iOS and Android examples open the built-in Settings app, so they run on a new account before you upload anything. To skip the config, run `npm install @revyl/verify` and save the example above as `verify/check.mts`, then run `node verify/check.mts`. [Getting started](https://github.com/RevylAI/verify/blob/v0.6.1/packages/verify/docs/getting-started.md) covers sign-in, your own app, and [CI](https://github.com/RevylAI/verify/blob/v0.6.1/packages/verify/docs/ci.md).
45
45
 
46
- For finished projects, see [`examples/`](https://github.com/RevylAI/verify/tree/v0.5.0/examples/): a [web store](https://github.com/RevylAI/verify/tree/v0.5.0/examples/web-store/), [mobile Settings](https://github.com/RevylAI/verify/tree/v0.5.0/examples/mobile-settings/), a [Mac workspace](https://github.com/RevylAI/verify/tree/v0.5.0/examples/mac-workspace/), a small [agent platform](https://github.com/RevylAI/verify/tree/v0.5.0/examples/agent-platform/) that runs web and iOS side by side, and [your own model](https://github.com/RevylAI/verify/tree/v0.5.0/examples/byo-model/) (with [Python](https://github.com/RevylAI/verify/blob/v0.5.0/python/examples/byo_model.py) and [Go](https://github.com/RevylAI/verify/blob/v0.5.0/go/examples/byomodel/main.go) versions).
46
+ For finished projects, see [`examples/`](https://github.com/RevylAI/verify/tree/v0.6.1/examples/): a [web store](https://github.com/RevylAI/verify/tree/v0.6.1/examples/web-store/), [mobile Settings](https://github.com/RevylAI/verify/tree/v0.6.1/examples/mobile-settings/), a [Mac workspace](https://github.com/RevylAI/verify/tree/v0.6.1/examples/mac-workspace/), a small [agent platform](https://github.com/RevylAI/verify/tree/v0.6.1/examples/agent-platform/) that runs web and iOS side by side, and [your own model](https://github.com/RevylAI/verify/tree/v0.6.1/examples/byo-model/) (with [Python](https://github.com/RevylAI/verify/blob/v0.6.1/python/examples/byo_model.py) and [Go](https://github.com/RevylAI/verify/blob/v0.6.1/go/examples/byomodel/main.go) versions).
47
47
 
48
48
  ## Exact actions
49
49
 
@@ -60,7 +60,7 @@ await session.instruction('Accept the terms if they are shown'); // the age
60
60
  await session.validation('The welcome screen is shown');
61
61
  ```
62
62
 
63
- `doubleTap`, `typeFocused`, `back`, `goHome`, `openUrl`, `launchApp`, `locate` and `screenSize` round them out; [which call works where](#which-call-works-where) shows the platform limits. Early access. Since TypeScript (`@revyl/verify`) 0.4.0, Python (`revyl-verify`) 0.2.0 and Go v0.3.0. `doubleTap`, `longPress`, `drag`, `typeFocused`, `goHome`, and Web keys and modifiers in `pressKey`, are new in TypeScript 0.5.0, Python 0.3.0 and Go v0.4.0. The [Python](https://github.com/RevylAI/verify/blob/v0.5.0/python/README.md#exact-actions) and [Go](https://github.com/RevylAI/verify/blob/v0.5.0/go/README.md#exact-actions) READMEs show them with each language's names. The [web](https://github.com/RevylAI/verify/blob/v0.5.0/packages/verify/docs/web.md#exact-actions) and [mobile](https://github.com/RevylAI/verify/blob/v0.5.0/packages/verify/docs/mobile.md#exact-actions) guides have runnable scripts, and [api.md](https://github.com/RevylAI/verify/blob/v0.5.0/packages/verify/docs/api.md#exact-actions) lists every call.
63
+ `doubleTap`, `typeFocused`, `back`, `goHome`, `openUrl`, `launchApp`, `locate` and `screenSize` round them out; [which call works where](#which-call-works-where) shows the platform limits. Early access. Since TypeScript (`@revyl/verify`) 0.4.0, Python (`revyl-verify`) 0.2.0 and Go v0.3.0. `doubleTap`, `longPress`, `drag`, `typeFocused`, `goHome`, and Web keys and modifiers in `pressKey`, are new in TypeScript 0.5.0, Python 0.3.0 and Go v0.4.0. On a Mac computer's screen, `tap` (by point or by description), `screenSize`, `typeFocused` and `pressKey` are new in TypeScript 0.6.0, Python 0.4.0 and Go v0.5.0. The [Python](https://github.com/RevylAI/verify/blob/v0.6.1/python/README.md#exact-actions) and [Go](https://github.com/RevylAI/verify/blob/v0.6.1/go/README.md#exact-actions) READMEs show them with each language's names. The [web](https://github.com/RevylAI/verify/blob/v0.6.1/packages/verify/docs/web.md#exact-actions) and [mobile](https://github.com/RevylAI/verify/blob/v0.6.1/packages/verify/docs/mobile.md#exact-actions) guides have runnable scripts, and [api.md](https://github.com/RevylAI/verify/blob/v0.6.1/packages/verify/docs/api.md#exact-actions) lists every call.
64
64
 
65
65
  ## Your own model
66
66
 
@@ -74,7 +74,7 @@ await session.instruction('Open Privacy', { model: 'openai/gpt-5.5' });
74
74
  await open('ios', { model: { id: 'qwen-3.8-27b', baseURL: 'https://api.cerebras.ai/v1', apiKey: process.env.CEREBRAS_API_KEY } });
75
75
  ```
76
76
 
77
- Since TypeScript (`@revyl/verify`) 0.4.0, Python (`revyl-verify`) 0.2.0 and Go v0.3.0; the [Python](https://github.com/RevylAI/verify/blob/v0.5.0/python/README.md#your-own-model) and [Go](https://github.com/RevylAI/verify/blob/v0.5.0/go/README.md#your-own-model) READMEs show them with each language's names. Built in: `anthropic`, `openai`, `google`, `openrouter`, `groq`, `cerebras`, `xai` and `ollama`; anything OpenAI-compatible works through `baseURL`. Early access; [Use your own model](https://github.com/RevylAI/verify/blob/v0.5.0/packages/verify/docs/own-model.md) covers the providers, picking a model, what Revyl still sees, limits and troubleshooting.
77
+ Since TypeScript (`@revyl/verify`) 0.4.0, Python (`revyl-verify`) 0.2.0 and Go v0.3.0; the [Python](https://github.com/RevylAI/verify/blob/v0.6.1/python/README.md#your-own-model) and [Go](https://github.com/RevylAI/verify/blob/v0.6.1/go/README.md#your-own-model) READMEs show them with each language's names. Built in: `anthropic`, `openai`, `google`, `openrouter`, `groq`, `cerebras`, `xai` and `ollama`; anything OpenAI-compatible works through `baseURL`. Early access; [Use your own model](https://github.com/RevylAI/verify/blob/v0.6.1/packages/verify/docs/own-model.md) covers the providers, picking a model, what Revyl still sees, limits and troubleshooting.
78
78
 
79
79
  ## Packages
80
80
 
@@ -82,8 +82,8 @@ Since TypeScript (`@revyl/verify`) 0.4.0, Python (`revyl-verify`) 0.2.0 and Go v
82
82
  | --- | --- | --- |
83
83
  | [`@revyl/verify`](https://www.npmjs.com/package/@revyl/verify) | `npm install @revyl/verify` | TypeScript SDK and the `verify` CLI (`init`, `login`, `logout`, `whoami`). Published on npm, Node.js 22.18 or later. |
84
84
  | [`revyl-verify`](https://www.npmjs.com/package/revyl-verify) (npm) | `npm install revyl-verify` | Alias of `@revyl/verify` at the same version. |
85
- | [Go module](https://github.com/RevylAI/verify/tree/go/v0.4.0/go) | `go get github.com/RevylAI/verify/go@v0.4.0` | Published as the `go/v0.4.0` tag, Go 1.26.9 or later. Calls return errors instead of throwing. |
86
- | [`revyl-verify`](https://pypi.org/project/revyl-verify/) (PyPI) | `pip install revyl-verify` or `uv add revyl-verify` | Python SDK, published on PyPI. Python 3.10 or later, sync and asyncio APIs, no CLI; see the [Python guide](https://github.com/RevylAI/verify/blob/v0.5.0/python/README.md). |
85
+ | [Go module](https://github.com/RevylAI/verify/tree/go/v0.5.1/go) | `go get github.com/RevylAI/verify/go@v0.5.1` | Published as the `go/v0.5.1` tag, Go 1.26.9 or later. Calls return errors instead of throwing. |
86
+ | [`revyl-verify`](https://pypi.org/project/revyl-verify/) (PyPI) | `pip install revyl-verify` or `uv add revyl-verify` | Python SDK, published on PyPI. Python 3.10 or later, sync and asyncio APIs, no CLI; see the [Python guide](https://github.com/RevylAI/verify/blob/v0.6.1/python/README.md). |
87
87
 
88
88
  ## Platforms
89
89
 
@@ -92,38 +92,40 @@ Since TypeScript (`@revyl/verify`) 0.4.0, Python (`revyl-verify`) 0.2.0 and Go v
92
92
  | Web | Yes | Yes | — | Yes |
93
93
  | iOS simulator | Yes | Yes | — | Yes |
94
94
  | Android emulator | Yes | Yes | — | Yes |
95
- | Mac computer | — | — | Yes | Yes |
95
+ | Mac computer | — | Screen: `tap`, `typeFocused`, `pressKey` | Yes | Yes |
96
96
 
97
97
  ### Which call works where
98
98
 
99
99
  | Call | Web | iOS | Android | Mac computer |
100
100
  | --- | --- | --- | --- | --- |
101
- | `tap`, `doubleTap`, `longPress`, `type`, `swipe`, `drag`, `locate`, `screenSize` | Yes | Yes | Yes | No, use `exec()` and files |
102
- | `pressKey(key, { modifiers })` | Yes: `'enter'`, `'backspace'`, `'tab'`, `'escape'`, arrows and more, with modifiers | `'enter'` and `'backspace'` only | `'enter'` and `'backspace'` only | No |
103
- | `typeFocused(text)` | Yes, types into the focused field | No, use `type(location, text)` | No, use `type(location, text)` | No |
101
+ | `tap({ x, y })`, `tap({ target })`, `screenSize` | Yes | Yes | Yes | Yes |
102
+ | `locate` | Yes | Yes | Yes | No, use `tap({ target })`, which returns the point it clicked, or read the point off `screenshot()` |
103
+ | `doubleTap`, `longPress`, `type`, `swipe`, `drag` | Yes | Yes | Yes | No |
104
+ | `pressKey(key, { modifiers })` | Yes: `'enter'`, `'backspace'`, `'tab'`, `'escape'`, arrows and more, with modifiers | `'enter'` and `'backspace'` only | `'enter'` and `'backspace'` only | Yes: the Web keys and modifiers, where `'meta'` is Command |
105
+ | `typeFocused(text, { clear })` | Yes, types into the focused field | No, use `type(location, text)` | No, use `type(location, text)` | Yes, with `{ clear: false }` |
104
106
  | `back()` | Yes, browser history | No, tap the on-screen back control | Yes, the system back button | No |
105
107
  | `goHome()` | No, use `openUrl` or `navigate` | Yes, the home screen | Yes, the home screen | No |
106
108
  | `openUrl(url)` | Yes, loads an http(s) URL in the tab | Yes, a URL or deep link | Yes, a URL or deep link | No |
107
109
  | `launchApp(bundleId)` | No, use `openUrl` or `navigate` | Yes | Yes | No |
108
110
  | `navigate(url)` | Yes | No, use `openUrl` | No, use `openUrl` | No |
109
- | `instruction`, `validation`, your own model | Yes | Yes | Yes | No, use `exec()` |
111
+ | `instruction`, `validation`, your own model | Yes | Yes | Yes | No, use `exec()` and the screen calls above |
110
112
  | `screenshot()` | Yes | Yes | Yes | Yes |
111
113
  | `exec`, files, processes | No, open a Mac computer | No, open a Mac computer | No, open a Mac computer | Yes |
112
114
 
113
- **Coordinates.** Prefer a description, such as `tap({ target: 'Sign in button' })`: Revyl finds the element on the current screen, and you never handle coordinates. Otherwise a point is `{ x, y }` in screenshot pixels on every platform: whole numbers from the top-left corner of the image `screenshot()` returns. So a position you read off a screenshot goes to `tap()`, `type()`, `swipe()` and every other exact action unchanged. `screenSize()` returns the screenshot's width and height, and `locate()` and the points `tap()`, `doubleTap()`, `longPress()` and `type()` return use the same pixels. A point outside the screenshot is refused with `INVALID_ARGUMENT` before any action is sent, and the message names the valid range. On iOS, Verify converts the pixels to the screen's points for you. On Web they equal CSS pixels at the default device scale factor of 1, and you still pass screenshot pixels at any other. This holds from TypeScript 0.4.1, Python 0.2.1 and Go v0.3.1; earlier versions took iOS points.
115
+ **Coordinates.** Prefer a description, such as `tap({ target: 'Sign in button' })`: Revyl finds the element on the current screen, and you never handle coordinates. Otherwise a point is `{ x, y }` in screenshot pixels on every platform: whole numbers from the top-left corner of the image `screenshot()` returns. So a position you read off a screenshot goes to `tap()`, `type()`, `swipe()` and every other exact action unchanged. `screenSize()` returns the screenshot's width and height, and `locate()` and the points `tap()`, `doubleTap()`, `longPress()` and `type()` return use the same pixels. A point outside the screenshot is refused with `INVALID_ARGUMENT` before any action is sent, and the message names the valid range. On iOS, Verify converts the pixels to the screen's points for you. On Web they equal CSS pixels at the default device scale factor of 1, and you still pass screenshot pixels at any other. This holds from TypeScript 0.4.1, Python 0.2.1 and Go v0.3.1; earlier versions took iOS points. On a Mac computer a point is in screenshot pixels too, and `tap({ target })` returns the point it clicked in the same pixels. Verify refuses a negative or fractional point before sending it, and Revyl refuses one past the screen's edge without clicking; both throw `INVALID_ARGUMENT` naming the valid range.
114
116
 
115
117
  A call a platform doesn't support throws `NOT_AVAILABLE_ON_PLATFORM`, and its message names the alternative.
116
118
 
117
- Web is available to every organization. Mac computers are early access and have a server-bounded lifetime; see [computers](https://github.com/RevylAI/verify/blob/v0.5.0/packages/verify/docs/computers.md).
119
+ Web is available to every organization. Mac computers are available to organizations on a current paid Revyl plan and have a server-bounded lifetime; see [computers](https://github.com/RevylAI/verify/blob/v0.6.1/packages/verify/docs/computers.md).
118
120
 
119
121
  ## Documentation
120
122
 
121
- The [documentation](https://github.com/RevylAI/verify/tree/v0.5.0/packages/verify/docs/) covers the [API](https://github.com/RevylAI/verify/blob/v0.5.0/packages/verify/docs/api.md), [configuration](https://github.com/RevylAI/verify/blob/v0.5.0/packages/verify/docs/configuration.md), [web](https://github.com/RevylAI/verify/blob/v0.5.0/packages/verify/docs/web.md), [mobile](https://github.com/RevylAI/verify/blob/v0.5.0/packages/verify/docs/mobile.md), [computers](https://github.com/RevylAI/verify/blob/v0.5.0/packages/verify/docs/computers.md), [CI](https://github.com/RevylAI/verify/blob/v0.5.0/packages/verify/docs/ci.md), [your own model](https://github.com/RevylAI/verify/blob/v0.5.0/packages/verify/docs/own-model.md), [errors and troubleshooting](https://github.com/RevylAI/verify/blob/v0.5.0/packages/verify/docs/errors.md), [coding agents](https://github.com/RevylAI/verify/blob/v0.5.0/packages/verify/docs/agents.md), and [live end-to-end results](https://github.com/RevylAI/verify/blob/v0.5.0/packages/verify/docs/end-to-end.md). The npm package ships these pages in `node_modules/@revyl/verify/docs/`, with an [llms.txt](https://github.com/RevylAI/verify/blob/v0.5.0/packages/verify/docs/llms.txt) index, so a coding agent can read the docs for the version you installed. The Revyl docs site has an overview at [docs.revyl.com/verify](https://docs.revyl.com/verify).
123
+ The [documentation](https://github.com/RevylAI/verify/tree/v0.6.1/packages/verify/docs/) covers the [API](https://github.com/RevylAI/verify/blob/v0.6.1/packages/verify/docs/api.md), [configuration](https://github.com/RevylAI/verify/blob/v0.6.1/packages/verify/docs/configuration.md), [web](https://github.com/RevylAI/verify/blob/v0.6.1/packages/verify/docs/web.md), [mobile](https://github.com/RevylAI/verify/blob/v0.6.1/packages/verify/docs/mobile.md), [computers](https://github.com/RevylAI/verify/blob/v0.6.1/packages/verify/docs/computers.md), [CI](https://github.com/RevylAI/verify/blob/v0.6.1/packages/verify/docs/ci.md), [your own model](https://github.com/RevylAI/verify/blob/v0.6.1/packages/verify/docs/own-model.md), [errors and troubleshooting](https://github.com/RevylAI/verify/blob/v0.6.1/packages/verify/docs/errors.md), [coding agents](https://github.com/RevylAI/verify/blob/v0.6.1/packages/verify/docs/agents.md), and [live end-to-end results](https://github.com/RevylAI/verify/blob/v0.6.1/packages/verify/docs/end-to-end.md). The npm package ships these pages in `node_modules/@revyl/verify/docs/`, with an [llms.txt](https://github.com/RevylAI/verify/blob/v0.6.1/packages/verify/docs/llms.txt) index, so a coding agent can read the docs for the version you installed. The Revyl docs site has an overview at [docs.revyl.com/verify](https://docs.revyl.com/verify).
122
124
 
123
125
  ## Status
124
126
 
125
- Verify is at 0.x, so APIs can still change between minor releases; the [changelog](https://github.com/RevylAI/verify/blob/v0.5.0/CHANGELOG.md) records each release. Mac computers are early access. See [Contributing](https://github.com/RevylAI/verify/blob/v0.5.0/CONTRIBUTING.md) and [Releasing](https://github.com/RevylAI/verify/blob/v0.5.0/RELEASING.md) to work on Verify.
127
+ Verify is at 0.x, so APIs can still change between minor releases; the [changelog](https://github.com/RevylAI/verify/blob/v0.6.1/CHANGELOG.md) records each release. See [Contributing](https://github.com/RevylAI/verify/blob/v0.6.1/CONTRIBUTING.md) and [Releasing](https://github.com/RevylAI/verify/blob/v0.6.1/RELEASING.md) to work on Verify.
126
128
 
127
129
  ## License
128
130
 
129
- [Apache 2.0](https://github.com/RevylAI/verify/blob/v0.5.0/LICENSE).
131
+ [Apache 2.0](https://github.com/RevylAI/verify/blob/v0.6.1/LICENSE).
@@ -46,8 +46,12 @@ export class RevylClient {
46
46
  throw apiError(method, path, response);
47
47
  return parseJson(response, `${method} ${path}`);
48
48
  }
49
- startDevice(request, signal) {
50
- return this.json('POST', '/api/v1/execution/start_device', { body: request, retry: false, timeoutMs: 120_000, signal });
49
+ async startDevice(request, signal) {
50
+ const path = '/api/v1/execution/start_device';
51
+ const response = await this.send('POST', path, { body: request, retry: false, timeoutMs: 120_000, signal });
52
+ if (response.status < 200 || response.status >= 300)
53
+ throw (request.platform !== 'web' && mobileStartRefusal(response)) || apiError('POST', path, response);
54
+ return parseJson(response, `POST ${path}`);
51
55
  }
52
56
  getDeviceSession(sessionId, signal) {
53
57
  return this.json('GET', `/api/v1/execution/device-sessions/${encodeURIComponent(sessionId)}`, { signal });
@@ -216,6 +220,21 @@ const START_ERROR_DETAILS = new Set([
216
220
  export function responseRefusalReason(response) {
217
221
  return responseKnownDetail(response, REFUSAL_REASONS);
218
222
  }
223
+ function mobileStartRefusal(response) {
224
+ if (response.status !== 400 && response.status !== 422 || edgeBlockedError(response))
225
+ return undefined;
226
+ let detail;
227
+ try {
228
+ detail = JSON.parse(new TextDecoder().decode(response.body))?.detail;
229
+ }
230
+ catch {
231
+ return undefined;
232
+ }
233
+ if (typeof detail !== 'string' || /^[a-z0-9_]*$/.test(detail.trim()))
234
+ return undefined;
235
+ const message = detail.trim().slice(0, 500);
236
+ return new VerifyError('INVALID_ARGUMENT', `Revyl refused to start the session (HTTP ${response.status}): ${/[.!?]$/.test(message) ? message : `${message}.`} Change the target to a value Revyl accepts and run again.`, { httpStatus: response.status });
237
+ }
219
238
  function responseKnownDetail(response, codes) {
220
239
  try {
221
240
  const parsed = JSON.parse(new TextDecoder().decode(response.body));
package/dist/cli/init.js CHANGED
@@ -99,9 +99,9 @@ ${target}
99
99
  });
100
100
  `;
101
101
  }
102
- function scriptTemplate({ platform, appId }) {
102
+ function scriptTemplate({ platform, appId, url }) {
103
103
  if (platform === 'mac') {
104
- return `// Mac computers are in early access; ask support@revyl.ai to enable your organization.
104
+ return `// Mac computers need a current paid Revyl plan; if yours can't start one, ask support@revyl.ai.
105
105
  import assert from 'node:assert/strict';
106
106
  import { open } from '@revyl/verify';
107
107
 
@@ -117,16 +117,19 @@ try {
117
117
  }
118
118
  `;
119
119
  }
120
- const steps = platform === 'web'
120
+ const steps = platform === 'web' && (url === undefined || /^https:\/\/example\.com\/?$/.test(url))
121
121
  ? `await session.instruction('Follow the link on the page');
122
122
  await session.validation('A page about domains reserved for documentation is shown');`
123
- : !appId && platform === 'ios'
124
- ? `await session.instruction('Open General, then About');
123
+ : platform === 'web'
124
+ ? `// Checks only that ${url} loads. Replace it with steps for your app, such as an instruction followed by a validation.
125
+ await session.validation('The page has finished loading and shows its content, not an error page');`
126
+ : !appId && platform === 'ios'
127
+ ? `await session.instruction('Open General, then About');
125
128
  await session.validation('The screen shows the operating system version');`
126
- : !appId && platform === 'android'
127
- ? `await session.instruction('Open the screen that shows information about this device, such as About or About phone, then scroll or navigate until the operating system version is visible');
129
+ : !appId && platform === 'android'
130
+ ? `await session.instruction('Open the screen that shows information about this device, such as About or About phone, then scroll or navigate until the operating system version is visible');
128
131
  await session.validation('The screen shows the version of the operating system');`
129
- : `await session.instruction('Wait for the app to finish opening and show its first screen');
132
+ : `await session.instruction('Wait for the app to finish opening and show its first screen');
130
133
  await session.validation('The app has opened and shows its first screen without an error');`;
131
134
  return `import { open } from '${PACKAGE_NAME}';
132
135
 
package/dist/cli/main.js CHANGED
@@ -126,7 +126,7 @@ async function initCommand(args) {
126
126
  process.stdout.write(`Created ${result.created.join(' and ')}${result.gitignoreUpdated ? ', and added .verify/ to .gitignore' : ''}.\n`);
127
127
  await signInAndCheck(interactive);
128
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']
129
+ ? ["Mac computers need a current paid Revyl plan; if yours can't start one, ask support@revyl.ai.", 'Run: node verify/example.mts']
130
130
  : answers.appId
131
131
  ? ['Run: node verify/example.mts']
132
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.'];
@@ -1,6 +1,7 @@
1
1
  import { RevylClient } from './api/client.js';
2
2
  import type { ComputerPlatform } from './api/types.js';
3
3
  import { type HostedTargetConfig, type ResolvedComputerTarget } from './config.js';
4
+ import { type Key, type KeyModifier, type Location, type Point } from './agent/loop.js';
4
5
  import { type ComputerProcess, type ComputerRecording, type ExecOptions, type ExecResult, type FileStat, type OpenedSession, type OperationOptions, type Session, type SessionOptions, type StepResult } from './session.js';
5
6
  export declare class ComputerSession implements Session {
6
7
  private readonly client;
@@ -27,20 +28,32 @@ export declare class ComputerSession implements Session {
27
28
  open(target: HostedTargetConfig): Promise<OpenedSession>;
28
29
  report(): Promise<Record<string, unknown>>;
29
30
  navigate(): Promise<never>;
30
- tap(): Promise<never>;
31
+ /** Clicks a point in screenshot pixels, or an element Revyl finds from a description, with the left button. Returns the point clicked. */
32
+ tap(at: Location): Promise<Point>;
33
+ private tapTarget;
31
34
  type(): Promise<never>;
32
- typeFocused(): Promise<never>;
35
+ /** Types into whatever has keyboard focus. Computers can't clear a field, so pass { clear: false }. */
36
+ typeFocused(text: string, options?: {
37
+ clear?: boolean;
38
+ }): Promise<void>;
33
39
  doubleTap(): Promise<never>;
34
40
  longPress(): Promise<never>;
35
41
  swipe(): Promise<never>;
36
42
  drag(): Promise<never>;
37
- pressKey(): Promise<never>;
43
+ /** Presses a key with modifiers held. 'meta' is the Command key on a Mac computer, and 'alt' is Option. */
44
+ pressKey(key: Key | string, options?: {
45
+ modifiers?: KeyModifier[];
46
+ }): Promise<void>;
38
47
  back(): Promise<never>;
39
48
  goHome(): Promise<never>;
40
49
  openUrl(): Promise<never>;
41
50
  launchApp(): Promise<never>;
42
51
  locate(): Promise<never>;
43
- screenSize(): Promise<never>;
52
+ /** The screenshot's size in pixels, the space tap() uses. */
53
+ screenSize(): Promise<{
54
+ width: number;
55
+ height: number;
56
+ }>;
44
57
  get expiresAt(): Date | undefined;
45
58
  recording(): Promise<ComputerRecording | null>;
46
59
  instruction(): Promise<never>;
@@ -58,6 +71,7 @@ export declare class ComputerSession implements Session {
58
71
  [Symbol.asyncDispose](): Promise<void>;
59
72
  private operation;
60
73
  private request;
74
+ private act;
61
75
  private parseReport;
62
76
  private assertOpen;
63
77
  private observationSignal;