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.
- use_computer/__init__.py +93 -0
- use_computer/actions.py +184 -0
- use_computer/backends/__init__.py +31 -0
- use_computer/backends/base.py +66 -0
- use_computer/backends/local.py +214 -0
- use_computer/backends/vnc.py +186 -0
- use_computer/cli.py +676 -0
- use_computer/compare.py +121 -0
- use_computer/config.py +532 -0
- use_computer/coordinates.py +104 -0
- use_computer/errors.py +53 -0
- use_computer/keys.py +209 -0
- use_computer/runner.py +304 -0
- use_computer/skill/SKILL.md +118 -0
- use_computer/skill/__init__.py +185 -0
- use_computer_cli-0.1.0.dist-info/METADATA +150 -0
- use_computer_cli-0.1.0.dist-info/RECORD +20 -0
- use_computer_cli-0.1.0.dist-info/WHEEL +4 -0
- use_computer_cli-0.1.0.dist-info/entry_points.txt +2 -0
- use_computer_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -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,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.
|