use-computer-cli 0.1.0__tar.gz
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.
- use_computer_cli-0.1.0/.gitignore +47 -0
- use_computer_cli-0.1.0/LICENSE +21 -0
- use_computer_cli-0.1.0/PKG-INFO +150 -0
- use_computer_cli-0.1.0/README.md +94 -0
- use_computer_cli-0.1.0/pyproject.toml +86 -0
- use_computer_cli-0.1.0/src/use_computer/__init__.py +93 -0
- use_computer_cli-0.1.0/src/use_computer/actions.py +184 -0
- use_computer_cli-0.1.0/src/use_computer/backends/__init__.py +31 -0
- use_computer_cli-0.1.0/src/use_computer/backends/base.py +66 -0
- use_computer_cli-0.1.0/src/use_computer/backends/local.py +214 -0
- use_computer_cli-0.1.0/src/use_computer/backends/vnc.py +186 -0
- use_computer_cli-0.1.0/src/use_computer/cli.py +676 -0
- use_computer_cli-0.1.0/src/use_computer/compare.py +121 -0
- use_computer_cli-0.1.0/src/use_computer/config.py +532 -0
- use_computer_cli-0.1.0/src/use_computer/coordinates.py +104 -0
- use_computer_cli-0.1.0/src/use_computer/errors.py +53 -0
- use_computer_cli-0.1.0/src/use_computer/keys.py +209 -0
- use_computer_cli-0.1.0/src/use_computer/runner.py +304 -0
- use_computer_cli-0.1.0/src/use_computer/skill/SKILL.md +118 -0
- use_computer_cli-0.1.0/src/use_computer/skill/__init__.py +185 -0
- use_computer_cli-0.1.0/tests/__init__.py +1 -0
- use_computer_cli-0.1.0/tests/conftest.py +76 -0
- use_computer_cli-0.1.0/tests/fake_backend.py +91 -0
- use_computer_cli-0.1.0/tests/test_backends.py +117 -0
- use_computer_cli-0.1.0/tests/test_cli.py +243 -0
- use_computer_cli-0.1.0/tests/test_compare.py +60 -0
- use_computer_cli-0.1.0/tests/test_config.py +167 -0
- use_computer_cli-0.1.0/tests/test_config_init.py +228 -0
- use_computer_cli-0.1.0/tests/test_coordinates.py +62 -0
- use_computer_cli-0.1.0/tests/test_keys.py +56 -0
- use_computer_cli-0.1.0/tests/test_runner.py +191 -0
- use_computer_cli-0.1.0/tests/test_skill.py +129 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# --- sdd -------------------------------------------------------------------
|
|
2
|
+
# config.yaml carries the sdd remote api-key. This repository is public, so it
|
|
3
|
+
# stays out. If you want the project description versioned, commit this file
|
|
4
|
+
# only after moving the key elsewhere.
|
|
5
|
+
.sdd/config.yaml
|
|
6
|
+
|
|
7
|
+
# --- local project config (use-computer's own .use-computer/) --------------
|
|
8
|
+
# This is a scratch config for driving the tool during development, not part of
|
|
9
|
+
# the project. It carries allow-local, which is a per-machine opt-in and must
|
|
10
|
+
# never arrive pre-enabled for whoever clones this.
|
|
11
|
+
.use-computer/
|
|
12
|
+
.env
|
|
13
|
+
.env.*
|
|
14
|
+
!.env.example
|
|
15
|
+
|
|
16
|
+
# --- Python ----------------------------------------------------------------
|
|
17
|
+
__pycache__/
|
|
18
|
+
*.py[cod]
|
|
19
|
+
*$py.class
|
|
20
|
+
*.egg-info/
|
|
21
|
+
*.egg
|
|
22
|
+
build/
|
|
23
|
+
dist/
|
|
24
|
+
.eggs/
|
|
25
|
+
.venv/
|
|
26
|
+
venv/
|
|
27
|
+
env/
|
|
28
|
+
uv.lock
|
|
29
|
+
|
|
30
|
+
# --- Tooling ---------------------------------------------------------------
|
|
31
|
+
.mypy_cache/
|
|
32
|
+
.ruff_cache/
|
|
33
|
+
.pytest_cache/
|
|
34
|
+
.coverage
|
|
35
|
+
htmlcov/
|
|
36
|
+
|
|
37
|
+
# --- Node (sdd is an npm CLI) ---------------------------------------------
|
|
38
|
+
node_modules/
|
|
39
|
+
|
|
40
|
+
# --- Screenshots captured while driving the tool by hand -------------------
|
|
41
|
+
*.png
|
|
42
|
+
!product/**/*.png
|
|
43
|
+
|
|
44
|
+
# --- Editor / OS -----------------------------------------------------------
|
|
45
|
+
.DS_Store
|
|
46
|
+
.vscode/
|
|
47
|
+
.idea/
|
|
@@ -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.
|
|
@@ -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,94 @@
|
|
|
1
|
+
# use-computer
|
|
2
|
+
|
|
3
|
+
Executes input on a screen for computer-use agents: move, click, double-click, right-click,
|
|
4
|
+
drag, scroll, type text, press key combinations, and capture a screenshot.
|
|
5
|
+
|
|
6
|
+
`use-computer` is the **acting** half of a pair. [ui-locator](https://github.com/applica-software-guru/ui-locator)
|
|
7
|
+
answers *where* the Invia button is and returns pixel coordinates; `use-computer` performs the
|
|
8
|
+
click there. Both are driven by another AI agent through a CLI that emits JSON on stdout and
|
|
9
|
+
diagnostics on stderr, with a Python API underneath.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pip install use-computer-cli # no backend
|
|
15
|
+
pip install "use-computer-cli[local]" # drive this machine's display (pynput + mss)
|
|
16
|
+
pip install "use-computer-cli[vnc]" # drive a remote framebuffer over RFB (vncdotool)
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Backends are optional extras, imported lazily, so the package installs without them.
|
|
20
|
+
|
|
21
|
+
The distribution is `use-computer-cli` because `use-computer` is taken on PyPI by an unrelated
|
|
22
|
+
project. The command it installs is `use-computer`, and the package it imports is `use_computer`.
|
|
23
|
+
|
|
24
|
+
## Use
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
use-computer click --x 120 --y 340 --use staging
|
|
28
|
+
use-computer type --text "hello" --use staging
|
|
29
|
+
use-computer key ctrl+s --use staging
|
|
30
|
+
use-computer screenshot --use laptop --out shot.png
|
|
31
|
+
|
|
32
|
+
# a batch runs over one connection -- the default command, so `batch` may be omitted
|
|
33
|
+
echo '[{"action":"click","x":120,"y":340},{"action":"key","combo":"enter"}]' \
|
|
34
|
+
| use-computer - --use staging --verify
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
stdout is one JSON object per run; every diagnostic goes to stderr. Exit codes: `0` success,
|
|
38
|
+
`1` failure, `2` bad usage.
|
|
39
|
+
|
|
40
|
+
## Three problems it solves
|
|
41
|
+
|
|
42
|
+
- **Coordinate spaces.** A screenshot on a HiDPI display is larger than the space the OS clicks
|
|
43
|
+
in. Every coordinate carries its space, `use-computer` scales between them, and it refuses to
|
|
44
|
+
guess when the ratio is unknown.
|
|
45
|
+
- **Setup cost.** Opening a VNC connection dominates a single action, so one run performs a
|
|
46
|
+
batch of actions over one connection.
|
|
47
|
+
- **Blind actuation.** A click that lands on nothing looks exactly like a click that worked, so
|
|
48
|
+
`--verify` compares the screen before and after and reports whether it changed.
|
|
49
|
+
|
|
50
|
+
## Configure
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
use-computer config init # asks, then proves it works
|
|
54
|
+
use-computer config init --backend vnc --host 10.0.0.5 # doesn't ask
|
|
55
|
+
use-computer config init --backend local --allow-local
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
`config init` writes the file below, then opens the backend it just configured and reports the
|
|
59
|
+
screen geometry and scale — so a coordinate space whose ratio cannot be derived surfaces at setup
|
|
60
|
+
rather than at the first click that lands in the wrong place.
|
|
61
|
+
|
|
62
|
+
It writes `.use-computer/config.toml` at the project root (found by walking up, the way git finds
|
|
63
|
+
its own):
|
|
64
|
+
|
|
65
|
+
```toml
|
|
66
|
+
default-profile = "laptop"
|
|
67
|
+
delay = 0.1
|
|
68
|
+
|
|
69
|
+
[profiles.laptop]
|
|
70
|
+
backend = "local"
|
|
71
|
+
allow-local = true
|
|
72
|
+
|
|
73
|
+
[profiles.staging]
|
|
74
|
+
backend = "vnc"
|
|
75
|
+
host = "10.0.0.5"
|
|
76
|
+
port = 5900
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Secrets go in `.use-computer/.env`, which is not committed. `use-computer config show` prints
|
|
80
|
+
every resolved value, the layer it came from and the variable that would override it.
|
|
81
|
+
|
|
82
|
+
## The agent skill
|
|
83
|
+
|
|
84
|
+
Instructions for the calling agent ship inside the package and are installed from it, so they
|
|
85
|
+
always match the installed version:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
use-computer skill install --scope project
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Documentation
|
|
92
|
+
|
|
93
|
+
This package is developed with [SDD](https://github.com/applica-software-guru/sdd). The specs
|
|
94
|
+
it implements live in `product/` and `system/` at the repository root.
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "use-computer-cli"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Execute input on a screen for computer-use agents: move, click, drag, scroll, type, key, screenshot."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { file = "LICENSE" }
|
|
12
|
+
authors = [{ name = "Bruno Fortunato" }]
|
|
13
|
+
keywords = ["computer-use", "automation", "vnc", "agent", "screenshot"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 3 - Alpha",
|
|
16
|
+
"Environment :: Console",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"Programming Language :: Python :: 3.10",
|
|
19
|
+
"Programming Language :: Python :: 3.11",
|
|
20
|
+
"Programming Language :: Python :: 3.12",
|
|
21
|
+
"Typing :: Typed",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
# Floors are the versions actually tested in CI. typer 0.12 is explicitly unsupported:
|
|
25
|
+
# it predates the API this CLI relies on.
|
|
26
|
+
dependencies = [
|
|
27
|
+
"pydantic>=2.7",
|
|
28
|
+
"pydantic-settings>=2.2",
|
|
29
|
+
"typer>=0.16",
|
|
30
|
+
"rich>=13",
|
|
31
|
+
"pillow>=10",
|
|
32
|
+
"python-dotenv>=1.0",
|
|
33
|
+
"tomli>=2.0; python_version < '3.11'",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
[project.optional-dependencies]
|
|
37
|
+
# Backends are optional extras, imported lazily, so the package installs without them.
|
|
38
|
+
# pyautogui is deliberately not used: its last release (0.9.54) dates from 2023.
|
|
39
|
+
local = ["pynput>=1.8", "mss>=10"]
|
|
40
|
+
vnc = ["vncdotool>=1.3"]
|
|
41
|
+
dev = [
|
|
42
|
+
"pytest>=8",
|
|
43
|
+
"pytest-mock>=3.12",
|
|
44
|
+
"ruff>=0.6",
|
|
45
|
+
"mypy>=1.11",
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
[project.scripts]
|
|
49
|
+
use-computer = "use_computer.cli:main"
|
|
50
|
+
|
|
51
|
+
[project.urls]
|
|
52
|
+
Homepage = "https://github.com/applica-software-guru/use-computer"
|
|
53
|
+
|
|
54
|
+
[tool.hatch.build.targets.wheel]
|
|
55
|
+
packages = ["src/use_computer"]
|
|
56
|
+
|
|
57
|
+
[tool.hatch.build.targets.wheel.force-include]
|
|
58
|
+
# The agent skill ships inside the wheel. A copy at the repository root would not.
|
|
59
|
+
"src/use_computer/skill/SKILL.md" = "use_computer/skill/SKILL.md"
|
|
60
|
+
|
|
61
|
+
[tool.hatch.build.targets.sdist]
|
|
62
|
+
include = ["src/use_computer", "tests", "README.md", "LICENSE", "pyproject.toml"]
|
|
63
|
+
|
|
64
|
+
[tool.ruff]
|
|
65
|
+
line-length = 100
|
|
66
|
+
src = ["src", "tests"]
|
|
67
|
+
|
|
68
|
+
[tool.ruff.lint]
|
|
69
|
+
select = ["E", "F", "I", "UP", "B", "SIM", "C4"]
|
|
70
|
+
|
|
71
|
+
[tool.ruff.lint.per-file-ignores]
|
|
72
|
+
# typer idiom: Option()/Argument() defaults are function calls in the signature.
|
|
73
|
+
"src/use_computer/cli.py" = ["B008"]
|
|
74
|
+
|
|
75
|
+
[tool.mypy]
|
|
76
|
+
python_version = "3.10"
|
|
77
|
+
strict = true
|
|
78
|
+
files = ["src", "tests"]
|
|
79
|
+
|
|
80
|
+
[[tool.mypy.overrides]]
|
|
81
|
+
module = ["pynput.*", "mss.*", "vncdotool.*", "ApplicationServices.*", "Quartz.*", "tomli"]
|
|
82
|
+
ignore_missing_imports = true
|
|
83
|
+
|
|
84
|
+
[tool.pytest.ini_options]
|
|
85
|
+
testpaths = ["tests"]
|
|
86
|
+
addopts = "-q"
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""use-computer: execute input on a screen for computer-use agents.
|
|
2
|
+
|
|
3
|
+
The acting half of a pair. ui-locator answers where; this performs the action there.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
9
|
+
|
|
10
|
+
from use_computer.actions import (
|
|
11
|
+
Action,
|
|
12
|
+
ClickAction,
|
|
13
|
+
DoubleClickAction,
|
|
14
|
+
DragAction,
|
|
15
|
+
KeyAction,
|
|
16
|
+
MouseButton,
|
|
17
|
+
MoveAction,
|
|
18
|
+
RightClickAction,
|
|
19
|
+
ScreenshotAction,
|
|
20
|
+
ScrollAction,
|
|
21
|
+
ScrollDirection,
|
|
22
|
+
TypeAction,
|
|
23
|
+
)
|
|
24
|
+
from use_computer.backends import Backend, create_backend
|
|
25
|
+
from use_computer.compare import ChangeReport, Screenshot, compare
|
|
26
|
+
from use_computer.config import BackendProfile, ResolvedConfig, Settings
|
|
27
|
+
from use_computer.config import load as load_config
|
|
28
|
+
from use_computer.coordinates import Coordinate, CoordinateSpace, ScreenInfo, convert
|
|
29
|
+
from use_computer.errors import (
|
|
30
|
+
ActionFailedError,
|
|
31
|
+
BackendNotAvailableError,
|
|
32
|
+
ConfigError,
|
|
33
|
+
CoordinateSpaceError,
|
|
34
|
+
KeySyntaxError,
|
|
35
|
+
PermissionDeniedError,
|
|
36
|
+
UseComputerError,
|
|
37
|
+
)
|
|
38
|
+
from use_computer.keys import KeyCombo, parse_combo
|
|
39
|
+
from use_computer.runner import ActionResult, ErrorInfo, RunResult, Session, run_actions
|
|
40
|
+
|
|
41
|
+
#: The distribution name on PyPI, which differs from the import package: `use-computer` was
|
|
42
|
+
#: already taken there. importlib.metadata is keyed by the distribution, so this is the name
|
|
43
|
+
#: that must appear here.
|
|
44
|
+
DISTRIBUTION = "use-computer-cli"
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
__version__ = version(DISTRIBUTION)
|
|
48
|
+
except PackageNotFoundError: # pragma: no cover - source checkout without an install
|
|
49
|
+
__version__ = "0.0.0"
|
|
50
|
+
|
|
51
|
+
__all__ = [
|
|
52
|
+
"DISTRIBUTION",
|
|
53
|
+
"Action",
|
|
54
|
+
"ActionFailedError",
|
|
55
|
+
"ActionResult",
|
|
56
|
+
"Backend",
|
|
57
|
+
"BackendNotAvailableError",
|
|
58
|
+
"BackendProfile",
|
|
59
|
+
"ChangeReport",
|
|
60
|
+
"ClickAction",
|
|
61
|
+
"ConfigError",
|
|
62
|
+
"Coordinate",
|
|
63
|
+
"CoordinateSpace",
|
|
64
|
+
"CoordinateSpaceError",
|
|
65
|
+
"DoubleClickAction",
|
|
66
|
+
"DragAction",
|
|
67
|
+
"ErrorInfo",
|
|
68
|
+
"KeyAction",
|
|
69
|
+
"KeyCombo",
|
|
70
|
+
"KeySyntaxError",
|
|
71
|
+
"MouseButton",
|
|
72
|
+
"MoveAction",
|
|
73
|
+
"PermissionDeniedError",
|
|
74
|
+
"ResolvedConfig",
|
|
75
|
+
"RightClickAction",
|
|
76
|
+
"RunResult",
|
|
77
|
+
"ScreenInfo",
|
|
78
|
+
"Screenshot",
|
|
79
|
+
"ScreenshotAction",
|
|
80
|
+
"ScrollAction",
|
|
81
|
+
"ScrollDirection",
|
|
82
|
+
"Session",
|
|
83
|
+
"Settings",
|
|
84
|
+
"TypeAction",
|
|
85
|
+
"UseComputerError",
|
|
86
|
+
"__version__",
|
|
87
|
+
"compare",
|
|
88
|
+
"convert",
|
|
89
|
+
"create_backend",
|
|
90
|
+
"load_config",
|
|
91
|
+
"parse_combo",
|
|
92
|
+
"run_actions",
|
|
93
|
+
]
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"""The action models, and the resolution that happens before a backend sees them.
|
|
2
|
+
|
|
3
|
+
The action set is the whole surface of what use-computer does to a screen. Coordinate scaling
|
|
4
|
+
and key parsing are applied here, so that everything crossing the backend boundary is already
|
|
5
|
+
in actuation units and canonical key names.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from enum import Enum
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Annotated, Literal
|
|
13
|
+
|
|
14
|
+
from pydantic import BaseModel, Field, TypeAdapter, field_validator
|
|
15
|
+
|
|
16
|
+
from use_computer.coordinates import Coordinate, CoordinateSpace, ScreenInfo, convert
|
|
17
|
+
from use_computer.keys import KeyCombo, canonical, parse_combo
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class MouseButton(str, Enum):
|
|
21
|
+
LEFT = "left"
|
|
22
|
+
RIGHT = "right"
|
|
23
|
+
MIDDLE = "middle"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ScrollDirection(str, Enum):
|
|
27
|
+
UP = "up"
|
|
28
|
+
DOWN = "down"
|
|
29
|
+
LEFT = "left"
|
|
30
|
+
RIGHT = "right"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class BaseAction(BaseModel):
|
|
34
|
+
"""Fields every action shares."""
|
|
35
|
+
|
|
36
|
+
delay: float | None = Field(default=None, ge=0, description="Seconds to wait afterwards.")
|
|
37
|
+
verify: bool = Field(default=False, description="Compare the screen before and after.")
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def target(self) -> Coordinate | None:
|
|
41
|
+
"""The point this action acts on, or None if it has no coordinate."""
|
|
42
|
+
return None
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def origin(self) -> Coordinate | None:
|
|
46
|
+
"""The point this action starts from -- only a drag has one."""
|
|
47
|
+
return None
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class _Positioned(BaseAction):
|
|
51
|
+
"""An action that may carry a coordinate. Omitting it acts where the pointer already is."""
|
|
52
|
+
|
|
53
|
+
x: int | None = None
|
|
54
|
+
y: int | None = None
|
|
55
|
+
space: CoordinateSpace | None = None
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def target(self) -> Coordinate | None:
|
|
59
|
+
if self.x is None or self.y is None:
|
|
60
|
+
return None
|
|
61
|
+
space = self.space if self.space is not None else CoordinateSpace.SCREENSHOT
|
|
62
|
+
return Coordinate(x=self.x, y=self.y, space=space)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class MoveAction(_Positioned):
|
|
66
|
+
action: Literal["move"] = "move"
|
|
67
|
+
x: int
|
|
68
|
+
y: int
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class ClickAction(_Positioned):
|
|
72
|
+
action: Literal["click"] = "click"
|
|
73
|
+
button: MouseButton = MouseButton.LEFT
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class DoubleClickAction(_Positioned):
|
|
77
|
+
action: Literal["double_click"] = "double_click"
|
|
78
|
+
button: MouseButton = MouseButton.LEFT
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class RightClickAction(_Positioned):
|
|
82
|
+
action: Literal["right_click"] = "right_click"
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class DragAction(BaseAction):
|
|
86
|
+
action: Literal["drag"] = "drag"
|
|
87
|
+
from_x: int
|
|
88
|
+
from_y: int
|
|
89
|
+
to_x: int
|
|
90
|
+
to_y: int
|
|
91
|
+
space: CoordinateSpace | None = None
|
|
92
|
+
button: MouseButton = MouseButton.LEFT
|
|
93
|
+
|
|
94
|
+
@property
|
|
95
|
+
def origin(self) -> Coordinate:
|
|
96
|
+
return Coordinate(x=self.from_x, y=self.from_y, space=self._space)
|
|
97
|
+
|
|
98
|
+
@property
|
|
99
|
+
def target(self) -> Coordinate:
|
|
100
|
+
return Coordinate(x=self.to_x, y=self.to_y, space=self._space)
|
|
101
|
+
|
|
102
|
+
@property
|
|
103
|
+
def _space(self) -> CoordinateSpace:
|
|
104
|
+
return self.space if self.space is not None else CoordinateSpace.SCREENSHOT
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class ScrollAction(_Positioned):
|
|
108
|
+
action: Literal["scroll"] = "scroll"
|
|
109
|
+
amount: int
|
|
110
|
+
direction: ScrollDirection = ScrollDirection.DOWN
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class TypeAction(BaseAction):
|
|
114
|
+
action: Literal["type"] = "type"
|
|
115
|
+
text: str
|
|
116
|
+
rate: float | None = Field(
|
|
117
|
+
default=None,
|
|
118
|
+
ge=0,
|
|
119
|
+
description="Seconds between keystrokes; None uses the configured rate.",
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class KeyAction(BaseAction):
|
|
124
|
+
action: Literal["key"] = "key"
|
|
125
|
+
combo: str
|
|
126
|
+
|
|
127
|
+
@field_validator("combo")
|
|
128
|
+
@classmethod
|
|
129
|
+
def _canonicalise(cls, value: str) -> str:
|
|
130
|
+
# Parse at construction: an unknown key name must fail before a connection is opened,
|
|
131
|
+
# and the canonical spelling is what appears in results and logs.
|
|
132
|
+
return canonical(value)
|
|
133
|
+
|
|
134
|
+
@property
|
|
135
|
+
def key_combo(self) -> KeyCombo:
|
|
136
|
+
return parse_combo(self.combo)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class ScreenshotAction(BaseAction):
|
|
140
|
+
action: Literal["screenshot"] = "screenshot"
|
|
141
|
+
out: Path | None = None
|
|
142
|
+
base64: bool = False
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
Action = Annotated[
|
|
146
|
+
MoveAction
|
|
147
|
+
| ClickAction
|
|
148
|
+
| DoubleClickAction
|
|
149
|
+
| RightClickAction
|
|
150
|
+
| DragAction
|
|
151
|
+
| ScrollAction
|
|
152
|
+
| TypeAction
|
|
153
|
+
| KeyAction
|
|
154
|
+
| ScreenshotAction,
|
|
155
|
+
Field(discriminator="action"),
|
|
156
|
+
]
|
|
157
|
+
|
|
158
|
+
#: Parses a batch file: a JSON array of action objects, discriminated on `action`.
|
|
159
|
+
ActionListAdapter: TypeAdapter[list[Action]] = TypeAdapter(list[Action])
|
|
160
|
+
|
|
161
|
+
#: Parses a single action object.
|
|
162
|
+
ActionAdapter: TypeAdapter[Action] = TypeAdapter(Action)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def with_default_space(action: Action, space: CoordinateSpace) -> Action:
|
|
166
|
+
"""Fill in the coordinate space an action did not state, from configuration."""
|
|
167
|
+
if getattr(action, "space", "missing") is None:
|
|
168
|
+
return action.model_copy(update={"space": space})
|
|
169
|
+
return action
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def resolve(action: Action, screen: ScreenInfo) -> tuple[Coordinate | None, Coordinate | None]:
|
|
173
|
+
"""Convert an action's coordinates into actuation units.
|
|
174
|
+
|
|
175
|
+
Returns ``(target, origin)``; either may be ``None`` when the action carries no coordinate.
|
|
176
|
+
|
|
177
|
+
Raises:
|
|
178
|
+
CoordinateSpaceError: when the scale needed for the conversion is unknown.
|
|
179
|
+
"""
|
|
180
|
+
target = action.target
|
|
181
|
+
origin = action.origin
|
|
182
|
+
resolved_target = convert(target, CoordinateSpace.ACTUATION, screen) if target else None
|
|
183
|
+
resolved_origin = convert(origin, CoordinateSpace.ACTUATION, screen) if origin else None
|
|
184
|
+
return resolved_target, resolved_origin
|