use-computer-cli 0.1.0__py3-none-any.whl

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.
@@ -0,0 +1,118 @@
1
+ ---
2
+ name: use-computer
3
+ description: Execute input on a screen — move, click, double-click, right-click, drag, scroll, type text, press key combinations, capture a screenshot. Use whenever you need to act on a GUI, locally or over VNC. Pair it with ui-locator, which tells you where to click.
4
+ x-skill-id: use-computer
5
+ x-skill-version: "1"
6
+ ---
7
+
8
+ # use-computer
9
+
10
+ You act on a screen through the `use-computer` CLI. It moves a real pointer and types real
11
+ keystrokes. It does not decide *what* to click — you do, usually with coordinates from
12
+ `ui-locator`.
13
+
14
+ ## Contract
15
+
16
+ - **stdout** is one JSON object per run. Parse it.
17
+ - **stderr** is diagnostics. Read it only when debugging.
18
+ - **exit codes**: `0` success, `1` failure, `2` bad usage.
19
+
20
+ Always pass `--use <profile>` unless a default profile is configured.
21
+
22
+ ## Coordinates: the thing that goes wrong
23
+
24
+ A screenshot on a HiDPI display is larger than the space the OS clicks in. Coordinates from
25
+ `ui-locator` are in **screenshot** pixels — the space it looked at. That is the default here,
26
+ so pass them through unchanged. Pass `--space actuation` only if you already converted them
27
+ yourself, which you should not do.
28
+
29
+ If a run fails saying the scale is unknown, take a screenshot first (`use-computer screenshot`)
30
+ and read `screen` from the result; do not compute a factor and retry with different numbers.
31
+
32
+ ## Acting
33
+
34
+ ```bash
35
+ use-computer click --x 120 --y 340 --use staging
36
+ use-computer double-click --x 120 --y 340 --use staging
37
+ use-computer right-click --x 120 --y 340 --use staging
38
+ use-computer move --x 120 --y 340 --use staging
39
+ use-computer drag --from-x 10 --from-y 20 --to-x 300 --to-y 400 --use staging
40
+ use-computer scroll --amount 3 --direction down --use staging
41
+ use-computer type --text "hello world" --use staging
42
+ use-computer key ctrl+s --use staging
43
+ use-computer screenshot --out shot.png --use staging
44
+ ```
45
+
46
+ `type` sends literal text. `ctrl+a` given to `type` types seven characters — use `key` for
47
+ shortcuts.
48
+
49
+ ## Key syntax
50
+
51
+ Modifiers `ctrl`, `alt`, `shift`, `cmd` (aliases: `control`, `option`, `super`, `win`, `meta`)
52
+ joined to a key with `+`: `ctrl+shift+t`, `cmd+space`, `alt+f4`, `enter`. Named keys: `enter`,
53
+ `tab`, `esc`, `space`, `backspace`, `delete`, `insert`, `home`, `end`, `pageup`, `pagedown`,
54
+ `up`, `down`, `left`, `right`, `f1`–`f24`. One spelling works on every backend.
55
+
56
+ ## Batch — prefer this
57
+
58
+ Opening a VNC connection costs more than the action does. Send the whole plan in one run; it
59
+ executes over one connection.
60
+
61
+ ```bash
62
+ echo '[
63
+ {"action":"click","x":120,"y":340,"verify":true},
64
+ {"action":"type","text":"hello","delay":0.2},
65
+ {"action":"key","combo":"enter"}
66
+ ]' | use-computer - --use staging
67
+ ```
68
+
69
+ `batch` is the default command, so `use-computer -` and `use-computer actions.json` work. A
70
+ batch stops at the first failure and reports `failed_index`, so you can resume from a known
71
+ point. `--continue-on-error` runs the rest anyway.
72
+
73
+ ## Verify — how you know it worked
74
+
75
+ A click that lands on nothing looks exactly like a click that worked. With `--verify` (or
76
+ `"verify": true` on one action) each action reports:
77
+
78
+ ```json
79
+ "change": {"changed": true, "magnitude": 0.18, "threshold": 0.002, "bbox": [40,120,600,400]}
80
+ ```
81
+
82
+ - `changed: false` after a click → the coordinate was probably stale. **Ask ui-locator again.
83
+ Do not click the same pixel twice.**
84
+ - `changed: true` with a tiny `magnitude` in a corner → a clock or a caret, not a response.
85
+
86
+ Verification costs two screenshots per action, so use it on the actions whose effect you need
87
+ to confirm, not on every one.
88
+
89
+ ## Before you act on something risky
90
+
91
+ `--dry-run` resolves and logs everything — profile, scaled coordinates, normalised keys —
92
+ without performing any of it. Results come back with `"performed": false`. Rehearse a batch you
93
+ are unsure about.
94
+
95
+ ## When it refuses
96
+
97
+ - **`BackendNotAvailableError`** — the extra is not installed. The message names it.
98
+ - **Local backend not enabled** — the `local` backend controls the user's own machine and needs
99
+ an explicit opt-in. Tell the user to set `allow-local = true` in the profile; do not work
100
+ around it.
101
+ - **Permission denied** — macOS Accessibility or Screen Recording. The message names which. Only
102
+ the user can grant it.
103
+ - **Coordinate space error** — see above. Take a screenshot; do not guess a factor.
104
+
105
+ ## Configuration
106
+
107
+ `use-computer config show` prints every resolved value, the layer it came from, and the exact
108
+ environment variable that would override it. Run it first when a profile behaves unexpectedly.
109
+
110
+ If there is no config at all, you can create one without a human:
111
+
112
+ ```bash
113
+ use-computer config init --backend vnc --host 10.0.0.5 --profile staging
114
+ ```
115
+
116
+ It refuses to overwrite an existing config, and it will not enable the local backend for you —
117
+ that opt-in is the user's to give. After writing, it opens the backend and reports the screen and
118
+ its scale; a failed probe means the config is on disk but wrong.
@@ -0,0 +1,185 @@
1
+ """The agent skill, and the commands that install it.
2
+
3
+ The skill ships as package data *inside* the package. A copy at the repository root would not
4
+ be in the wheel, and a skill that is not in the wheel does not exist for anyone who installed
5
+ from PyPI.
6
+
7
+ Nothing here ever writes to or deletes a directory that does not carry this skill's frontmatter
8
+ marker: removing a directory someone else owns is unrecoverable, and the marker is the proof of
9
+ ownership.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from enum import Enum
15
+ from importlib import resources
16
+ from pathlib import Path
17
+
18
+ from pydantic import BaseModel, ConfigDict
19
+
20
+ from use_computer.errors import ConfigError
21
+
22
+ #: Frontmatter key/value that marks a directory as owned by this skill.
23
+ MARKER_KEY = "x-skill-id"
24
+ MARKER_VALUE = "use-computer"
25
+
26
+ #: Directory name the skill is installed under.
27
+ SKILL_NAME = "use-computer"
28
+ SKILL_FILE = "SKILL.md"
29
+
30
+ #: The neutral layout, used when a project shows no preference.
31
+ NEUTRAL_DIR = Path(".agents") / "skills"
32
+ CLAUDE_DIR = Path(".claude") / "skills"
33
+
34
+
35
+ class Scope(str, Enum):
36
+ USER = "user"
37
+ PROJECT = "project"
38
+ AGENTS = "agents"
39
+ CLAUDE = "claude"
40
+
41
+
42
+ class SkillState(BaseModel):
43
+ """Where the skill is, and whether it matches the bundled copy."""
44
+
45
+ model_config = ConfigDict(frozen=True)
46
+
47
+ scope: Scope
48
+ path: Path
49
+ installed: bool
50
+ owned: bool
51
+ up_to_date: bool
52
+
53
+ @property
54
+ def status(self) -> str:
55
+ if not self.installed:
56
+ return "missing"
57
+ if not self.owned:
58
+ return "foreign"
59
+ return "up-to-date" if self.up_to_date else "outdated"
60
+
61
+
62
+ def bundled_text() -> str:
63
+ """The bundled SKILL.md, read as package data -- the package may be zipped."""
64
+ return resources.files(__package__).joinpath(SKILL_FILE).read_text(encoding="utf-8")
65
+
66
+
67
+ def skills_dir(scope: Scope, *, root: Path | None = None, override: Path | None = None) -> Path:
68
+ """The skills directory for a scope.
69
+
70
+ The ``project`` scope follows the layout the target project already uses, and defaults to
71
+ the neutral ``.agents/skills`` when neither exists.
72
+ """
73
+ if override is not None:
74
+ return override
75
+ base = root or Path.cwd()
76
+ if scope is Scope.CLAUDE:
77
+ return base / CLAUDE_DIR
78
+ if scope is Scope.AGENTS:
79
+ return base / NEUTRAL_DIR
80
+ if scope is Scope.PROJECT:
81
+ if (base / CLAUDE_DIR).is_dir():
82
+ return base / CLAUDE_DIR
83
+ if (base / NEUTRAL_DIR).is_dir():
84
+ return base / NEUTRAL_DIR
85
+ return base / NEUTRAL_DIR
86
+ home = Path.home()
87
+ if (home / CLAUDE_DIR).is_dir():
88
+ return home / CLAUDE_DIR
89
+ return home / NEUTRAL_DIR
90
+
91
+
92
+ def target_path(scope: Scope, *, root: Path | None = None, override: Path | None = None) -> Path:
93
+ return skills_dir(scope, root=root, override=override) / SKILL_NAME / SKILL_FILE
94
+
95
+
96
+ def carries_marker(path: Path) -> bool:
97
+ """Whether a SKILL.md declares this skill's frontmatter marker."""
98
+ if not path.is_file():
99
+ return False
100
+ try:
101
+ text = path.read_text(encoding="utf-8")
102
+ except OSError:
103
+ return False
104
+ return _marker_in_frontmatter(text)
105
+
106
+
107
+ def _marker_in_frontmatter(text: str) -> bool:
108
+ lines = text.splitlines()
109
+ if not lines or lines[0].strip() != "---":
110
+ return False
111
+ for line in lines[1:]:
112
+ if line.strip() == "---":
113
+ return False
114
+ key, _, value = line.partition(":")
115
+ if key.strip() == MARKER_KEY and value.strip().strip("\"'") == MARKER_VALUE:
116
+ return True
117
+ return False
118
+
119
+
120
+ def status(scope: Scope, *, root: Path | None = None, override: Path | None = None) -> SkillState:
121
+ path = target_path(scope, root=root, override=override)
122
+ installed = path.is_file()
123
+ owned = carries_marker(path)
124
+ up_to_date = False
125
+ if installed and owned:
126
+ up_to_date = path.read_text(encoding="utf-8") == bundled_text()
127
+ return SkillState(
128
+ scope=scope, path=path, installed=installed, owned=owned, up_to_date=up_to_date
129
+ )
130
+
131
+
132
+ def install(
133
+ scope: Scope,
134
+ *,
135
+ root: Path | None = None,
136
+ override: Path | None = None,
137
+ force: bool = False,
138
+ ) -> SkillState:
139
+ """Write the bundled skill into the scope's skills directory.
140
+
141
+ Raises:
142
+ ConfigError: when a file is already there and ``force`` was not given, or when the
143
+ existing file belongs to another skill.
144
+ """
145
+ path = target_path(scope, root=root, override=override)
146
+ if path.exists():
147
+ if not carries_marker(path):
148
+ raise ConfigError(
149
+ f"{path} exists and does not carry the {MARKER_KEY}: {MARKER_VALUE} marker, so "
150
+ "it belongs to something else. Refusing to touch it."
151
+ )
152
+ if not force:
153
+ raise ConfigError(f"{path} already exists. Pass --force to overwrite it.")
154
+ path.parent.mkdir(parents=True, exist_ok=True)
155
+ path.write_text(bundled_text(), encoding="utf-8")
156
+ return status(scope, root=root, override=override)
157
+
158
+
159
+ def update(
160
+ scope: Scope, *, root: Path | None = None, override: Path | None = None
161
+ ) -> SkillState:
162
+ """Refresh an installed skill. Installing over our own copy is what update means."""
163
+ path = target_path(scope, root=root, override=override)
164
+ if path.exists() and not carries_marker(path):
165
+ raise ConfigError(
166
+ f"{path} does not carry the {MARKER_KEY}: {MARKER_VALUE} marker. Refusing to touch it."
167
+ )
168
+ return install(scope, root=root, override=override, force=True)
169
+
170
+
171
+ def remove(scope: Scope, *, root: Path | None = None, override: Path | None = None) -> SkillState:
172
+ """Delete an installed skill, and only ever one that carries the marker."""
173
+ path = target_path(scope, root=root, override=override)
174
+ if not path.exists():
175
+ return status(scope, root=root, override=override)
176
+ if not carries_marker(path):
177
+ raise ConfigError(
178
+ f"{path} does not carry the {MARKER_KEY}: {MARKER_VALUE} marker, so it belongs to "
179
+ "something else. Refusing to remove it."
180
+ )
181
+ path.unlink()
182
+ parent = path.parent
183
+ if parent.is_dir() and not any(parent.iterdir()):
184
+ parent.rmdir()
185
+ return status(scope, root=root, override=override)
@@ -0,0 +1,150 @@
1
+ Metadata-Version: 2.5
2
+ Name: use-computer-cli
3
+ Version: 0.1.0
4
+ Summary: Execute input on a screen for computer-use agents: move, click, drag, scroll, type, key, screenshot.
5
+ Project-URL: Homepage, https://github.com/applica-software-guru/use-computer
6
+ Author: Bruno Fortunato
7
+ License: MIT License
8
+
9
+ Copyright (c) 2026 Bruno Fortunato
10
+
11
+ Permission is hereby granted, free of charge, to any person obtaining a copy
12
+ of this software and associated documentation files (the "Software"), to deal
13
+ in the Software without restriction, including without limitation the rights
14
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15
+ copies of the Software, and to permit persons to whom the Software is
16
+ furnished to do so, subject to the following conditions:
17
+
18
+ The above copyright notice and this permission notice shall be included in all
19
+ copies or substantial portions of the Software.
20
+
21
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27
+ SOFTWARE.
28
+ License-File: LICENSE
29
+ Keywords: agent,automation,computer-use,screenshot,vnc
30
+ Classifier: Development Status :: 3 - Alpha
31
+ Classifier: Environment :: Console
32
+ Classifier: Intended Audience :: Developers
33
+ Classifier: Programming Language :: Python :: 3.10
34
+ Classifier: Programming Language :: Python :: 3.11
35
+ Classifier: Programming Language :: Python :: 3.12
36
+ Classifier: Typing :: Typed
37
+ Requires-Python: >=3.10
38
+ Requires-Dist: pillow>=10
39
+ Requires-Dist: pydantic-settings>=2.2
40
+ Requires-Dist: pydantic>=2.7
41
+ Requires-Dist: python-dotenv>=1.0
42
+ Requires-Dist: rich>=13
43
+ Requires-Dist: tomli>=2.0; python_version < '3.11'
44
+ Requires-Dist: typer>=0.16
45
+ Provides-Extra: dev
46
+ Requires-Dist: mypy>=1.11; extra == 'dev'
47
+ Requires-Dist: pytest-mock>=3.12; extra == 'dev'
48
+ Requires-Dist: pytest>=8; extra == 'dev'
49
+ Requires-Dist: ruff>=0.6; extra == 'dev'
50
+ Provides-Extra: local
51
+ Requires-Dist: mss>=10; extra == 'local'
52
+ Requires-Dist: pynput>=1.8; extra == 'local'
53
+ Provides-Extra: vnc
54
+ Requires-Dist: vncdotool>=1.3; extra == 'vnc'
55
+ Description-Content-Type: text/markdown
56
+
57
+ # use-computer
58
+
59
+ Executes input on a screen for computer-use agents: move, click, double-click, right-click,
60
+ drag, scroll, type text, press key combinations, and capture a screenshot.
61
+
62
+ `use-computer` is the **acting** half of a pair. [ui-locator](https://github.com/applica-software-guru/ui-locator)
63
+ answers *where* the Invia button is and returns pixel coordinates; `use-computer` performs the
64
+ click there. Both are driven by another AI agent through a CLI that emits JSON on stdout and
65
+ diagnostics on stderr, with a Python API underneath.
66
+
67
+ ## Install
68
+
69
+ ```bash
70
+ pip install use-computer-cli # no backend
71
+ pip install "use-computer-cli[local]" # drive this machine's display (pynput + mss)
72
+ pip install "use-computer-cli[vnc]" # drive a remote framebuffer over RFB (vncdotool)
73
+ ```
74
+
75
+ Backends are optional extras, imported lazily, so the package installs without them.
76
+
77
+ The distribution is `use-computer-cli` because `use-computer` is taken on PyPI by an unrelated
78
+ project. The command it installs is `use-computer`, and the package it imports is `use_computer`.
79
+
80
+ ## Use
81
+
82
+ ```bash
83
+ use-computer click --x 120 --y 340 --use staging
84
+ use-computer type --text "hello" --use staging
85
+ use-computer key ctrl+s --use staging
86
+ use-computer screenshot --use laptop --out shot.png
87
+
88
+ # a batch runs over one connection -- the default command, so `batch` may be omitted
89
+ echo '[{"action":"click","x":120,"y":340},{"action":"key","combo":"enter"}]' \
90
+ | use-computer - --use staging --verify
91
+ ```
92
+
93
+ stdout is one JSON object per run; every diagnostic goes to stderr. Exit codes: `0` success,
94
+ `1` failure, `2` bad usage.
95
+
96
+ ## Three problems it solves
97
+
98
+ - **Coordinate spaces.** A screenshot on a HiDPI display is larger than the space the OS clicks
99
+ in. Every coordinate carries its space, `use-computer` scales between them, and it refuses to
100
+ guess when the ratio is unknown.
101
+ - **Setup cost.** Opening a VNC connection dominates a single action, so one run performs a
102
+ batch of actions over one connection.
103
+ - **Blind actuation.** A click that lands on nothing looks exactly like a click that worked, so
104
+ `--verify` compares the screen before and after and reports whether it changed.
105
+
106
+ ## Configure
107
+
108
+ ```bash
109
+ use-computer config init # asks, then proves it works
110
+ use-computer config init --backend vnc --host 10.0.0.5 # doesn't ask
111
+ use-computer config init --backend local --allow-local
112
+ ```
113
+
114
+ `config init` writes the file below, then opens the backend it just configured and reports the
115
+ screen geometry and scale — so a coordinate space whose ratio cannot be derived surfaces at setup
116
+ rather than at the first click that lands in the wrong place.
117
+
118
+ It writes `.use-computer/config.toml` at the project root (found by walking up, the way git finds
119
+ its own):
120
+
121
+ ```toml
122
+ default-profile = "laptop"
123
+ delay = 0.1
124
+
125
+ [profiles.laptop]
126
+ backend = "local"
127
+ allow-local = true
128
+
129
+ [profiles.staging]
130
+ backend = "vnc"
131
+ host = "10.0.0.5"
132
+ port = 5900
133
+ ```
134
+
135
+ Secrets go in `.use-computer/.env`, which is not committed. `use-computer config show` prints
136
+ every resolved value, the layer it came from and the variable that would override it.
137
+
138
+ ## The agent skill
139
+
140
+ Instructions for the calling agent ship inside the package and are installed from it, so they
141
+ always match the installed version:
142
+
143
+ ```bash
144
+ use-computer skill install --scope project
145
+ ```
146
+
147
+ ## Documentation
148
+
149
+ This package is developed with [SDD](https://github.com/applica-software-guru/sdd). The specs
150
+ it implements live in `product/` and `system/` at the repository root.
@@ -0,0 +1,20 @@
1
+ use_computer/__init__.py,sha256=fUBZ-Ci9StV2qm5L-4CYaxxucVbzoxbLh6kBl2N_NS0,2429
2
+ use_computer/actions.py,sha256=NUb1VGlZoR6wgbHjujUlAMDCtfhuiyP40ROCsNTF8x8,5353
3
+ use_computer/cli.py,sha256=5z446VAYA95bhcF2MSGfeU6lDmW1W8jAavDuPy2AEM4,22792
4
+ use_computer/compare.py,sha256=kgnGc5wP6RzGpuXXq26wa2yW2XK1d6fc7EJc_HnkBlU,4324
5
+ use_computer/config.py,sha256=eTDOuhCCl98QnQHQD10IYXT_gEBXXIiEymjIbqiaOqg,19510
6
+ use_computer/coordinates.py,sha256=qugbalE87_IE_yiZUw__YqUwjh9obH5sUIJHalncQE0,3969
7
+ use_computer/errors.py,sha256=J1lfkrOWPm_h-EDqNF0UaNhbp3VVxdKqtTtKeyDdSAo,1710
8
+ use_computer/keys.py,sha256=Z-xTLBIRAiG2prl0O-TenMUNj1eJpVvUpEgp32kDlPs,6171
9
+ use_computer/runner.py,sha256=U7TWPXEDaYKn5xtk1iAvv8_F5fhyCzJ04MfSZ7ICMqM,10296
10
+ use_computer/backends/__init__.py,sha256=siniU8D02IH2B_lCvpZ5AXzYV7rb4sLO1srjsW6osZg,1102
11
+ use_computer/backends/base.py,sha256=BLBBKGXSwXTSEQkzXQndtdbnv1cXBZEnEyaZoWycqBU,2230
12
+ use_computer/backends/local.py,sha256=AcsEd6pGzwQ2UNPf82YJgZ_bFGbs7ml8Jl24fQjWJNY,8168
13
+ use_computer/backends/vnc.py,sha256=4_7PK5rIBeJ8u3OaJ3jUNy5nLR8HrmYiS4UcWnhSOx8,6093
14
+ use_computer/skill/__init__.py,sha256=a_93vXTA99pNHdGxr_FD6r5rSPCP7MmDLkEBYj5VPUI,6230
15
+ use_computer/skill/SKILL.md,sha256=jLtV10F-0e31y6fYjUBVU8ElF3DsmW3ZaSovhmOsXJ0,4968
16
+ use_computer_cli-0.1.0.dist-info/METADATA,sha256=NcGyHn6s02oM5PXqndpz3JE4qkCrGEooZk0xE20iOiM,6085
17
+ use_computer_cli-0.1.0.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
18
+ use_computer_cli-0.1.0.dist-info/entry_points.txt,sha256=5k_dpdhwgzZSckS5mtXJvLMYC1s06myYEdbjWKeoQjg,55
19
+ use_computer_cli-0.1.0.dist-info/licenses/LICENSE,sha256=GwYgROcUjah7q6rGkOnEClxr39GXUXUZWyqog-iBw9c,1072
20
+ use_computer_cli-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ use-computer = use_computer.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Bruno Fortunato
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.