macos-harness 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.
- macos_harness-0.1.0/.github/workflows/ci.yml +20 -0
- macos_harness-0.1.0/.github/workflows/publish.yml +22 -0
- macos_harness-0.1.0/.gitignore +10 -0
- macos_harness-0.1.0/CONTRIBUTING.md +13 -0
- macos_harness-0.1.0/LICENSE +21 -0
- macos_harness-0.1.0/PKG-INFO +123 -0
- macos_harness-0.1.0/README.md +97 -0
- macos_harness-0.1.0/SECURITY.md +9 -0
- macos_harness-0.1.0/install.md +37 -0
- macos_harness-0.1.0/macos-harness +10 -0
- macos_harness-0.1.0/pyproject.toml +54 -0
- macos_harness-0.1.0/skills/macos-harness/SKILL.md +96 -0
- macos_harness-0.1.0/skills/macos-harness/agents/openai.yaml +4 -0
- macos_harness-0.1.0/src/macos_harness/__init__.py +12 -0
- macos_harness-0.1.0/src/macos_harness/browser.py +168 -0
- macos_harness-0.1.0/src/macos_harness/cli.py +158 -0
- macos_harness-0.1.0/src/macos_harness/controls.py +119 -0
- macos_harness-0.1.0/src/macos_harness/macos.py +1459 -0
- macos_harness-0.1.0/src/macos_harness/overlay.py +319 -0
- macos_harness-0.1.0/src/macos_harness/pointer.py +24 -0
- macos_harness-0.1.0/src/macos_harness/telemetry.py +195 -0
- macos_harness-0.1.0/static/banner.png +0 -0
- macos_harness-0.1.0/tests/test_browser.py +54 -0
- macos_harness-0.1.0/tests/test_macos.py +502 -0
- macos_harness-0.1.0/tests/test_overlay.py +77 -0
- macos_harness-0.1.0/tests/test_telemetry.py +58 -0
- macos_harness-0.1.0/uv.lock +515 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
pull_request:
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
test:
|
|
9
|
+
runs-on: macos-latest
|
|
10
|
+
strategy:
|
|
11
|
+
matrix:
|
|
12
|
+
python-version: ["3.11", "3.12"]
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
- uses: astral-sh/setup-uv@v6
|
|
16
|
+
with:
|
|
17
|
+
python-version: ${{ matrix.python-version }}
|
|
18
|
+
- run: uv sync --locked
|
|
19
|
+
- run: uv run ruff check .
|
|
20
|
+
- run: uv run pytest
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
name: Publish
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
release:
|
|
6
|
+
types: [published]
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
id-token: write
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
pypi:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
environment: pypi
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
- uses: astral-sh/setup-uv@v6
|
|
19
|
+
with:
|
|
20
|
+
python-version: "3.12"
|
|
21
|
+
- run: uv build
|
|
22
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
macOS Harness is intentionally small. Prefer exposing a raw macOS primitive over
|
|
4
|
+
adding an app-specific helper or workflow.
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
uv sync
|
|
8
|
+
uv run ruff check .
|
|
9
|
+
uv run pytest
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Pull requests should preserve three invariants: never move the physical pointer,
|
|
13
|
+
never activate or raise a target app, and never send user data through telemetry.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Browser Use
|
|
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,123 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: macos-harness
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: The simplest, thinnest harness that gives an LLM complete freedom to control a Mac.
|
|
5
|
+
Project-URL: Homepage, https://github.com/browser-use/macos-harness
|
|
6
|
+
Project-URL: Repository, https://github.com/browser-use/macos-harness
|
|
7
|
+
Project-URL: Issues, https://github.com/browser-use/macos-harness/issues
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: accessibility,agent,automation,cdp,computer-use,macos
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Environment :: MacOS X
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Operating System :: MacOS
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Topic :: Desktop Environment
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Requires-Python: >=3.11
|
|
22
|
+
Requires-Dist: browser-harness>=0.1.9
|
|
23
|
+
Requires-Dist: pillow>=11.3
|
|
24
|
+
Requires-Dist: pyobjc-framework-applicationservices>=12.0; sys_platform == 'darwin'
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
<img src="static/banner.png" alt="macOS Harness" width="100%" />
|
|
28
|
+
|
|
29
|
+
# macOS Harness ⌘
|
|
30
|
+
|
|
31
|
+
The simplest, thinnest harness that gives an LLM complete freedom to complete
|
|
32
|
+
virtually any task on a Mac.
|
|
33
|
+
|
|
34
|
+
The agent writes what is missing, mid-task. No framework, no recipes, no rails.
|
|
35
|
+
One Python process connected directly to macOS, your real browser, and your files.
|
|
36
|
+
|
|
37
|
+
```text
|
|
38
|
+
● agent: wants to do something no helper exists for
|
|
39
|
+
│
|
|
40
|
+
● sees the app and uses raw macOS primitives
|
|
41
|
+
│
|
|
42
|
+
● writes the missing logic in ordinary Python
|
|
43
|
+
│
|
|
44
|
+
✓ task complete no app-specific tool added
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**Your agent now has a Mac.**
|
|
48
|
+
|
|
49
|
+
## Give it to your agent
|
|
50
|
+
|
|
51
|
+
Paste this into Codex or Claude Code:
|
|
52
|
+
|
|
53
|
+
```text
|
|
54
|
+
Install or upgrade macOS Harness from https://github.com/browser-use/macos-harness with uv using Python 3.12. Register the skill printed by `macos-harness skill`, then run `macos-harness doctor`. Explain any missing macOS permissions and ask before requesting them. Finally, verify the harness by capturing one already-running app without bringing it to the foreground.
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
That is it. The agent installs the package, teaches itself the workflow, checks
|
|
58
|
+
permissions, and verifies the connection. [Manual setup](install.md) is available too.
|
|
59
|
+
|
|
60
|
+
## Six primitives. The whole Mac.
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
macos-harness <<'PY'
|
|
64
|
+
frame = mac.see("Spotify")
|
|
65
|
+
mac.key("cmd+k", app="Spotify")
|
|
66
|
+
mac.type("Alessia Cara", app="Spotify")
|
|
67
|
+
mac.click(640, 420, app="Spotify")
|
|
68
|
+
|
|
69
|
+
item = mac.ax.at(640, 420, app="Spotify")
|
|
70
|
+
mac.script('tell application "Spotify" to play')
|
|
71
|
+
|
|
72
|
+
print(browser.page_info())
|
|
73
|
+
print(list(Path.home().iterdir()))
|
|
74
|
+
PY
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Think in `see`, `key`, `type`, `click`, `ax`, and `script`. `browser`, `Path`, and
|
|
78
|
+
`subprocess` are ready in the same Python process.
|
|
79
|
+
|
|
80
|
+
There are no Spotify tools, Slack tools, or Final Cut tools. The model gets raw
|
|
81
|
+
primitives and writes the rest.
|
|
82
|
+
|
|
83
|
+
## How it works
|
|
84
|
+
|
|
85
|
+
```text
|
|
86
|
+
one persistent Python process
|
|
87
|
+
│
|
|
88
|
+
┌──────────────────────┼──────────────────────┐
|
|
89
|
+
│ │ │
|
|
90
|
+
mac.* browser.* Path / subprocess
|
|
91
|
+
│ │ │
|
|
92
|
+
┌───────────┼───────────┐ Browser Harness files + shell
|
|
93
|
+
│ │ │ │
|
|
94
|
+
CGWindow CGEvent AX + Apple CDP
|
|
95
|
+
screenshots to PID Events │
|
|
96
|
+
│ │ │ real Chrome
|
|
97
|
+
└───────────┴───────────┘
|
|
98
|
+
│
|
|
99
|
+
native + Electron apps
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
- Captures background app windows without bringing them forward
|
|
103
|
+
- Sends keyboard and coordinate input directly to an app PID
|
|
104
|
+
- Draws an animated, click-through pointer without moving your real cursor
|
|
105
|
+
- Exposes raw Apple Accessibility and Apple Events when vision is not enough
|
|
106
|
+
- Uses Browser Harness for the real, logged-in browser
|
|
107
|
+
- Keeps ordinary Python and the local filesystem within reach
|
|
108
|
+
|
|
109
|
+
## Permissions and privacy
|
|
110
|
+
|
|
111
|
+
`macos-harness doctor` reports the macOS permissions actually needed. The harness
|
|
112
|
+
never activates or raises a target app and never moves the physical pointer.
|
|
113
|
+
|
|
114
|
+
Anonymous telemetry is enabled by default. It records only the CLI command
|
|
115
|
+
category, success, duration, package version, OS/architecture, and detected agent
|
|
116
|
+
client. It never records prompts, app names, screenshots, UI text, scripts, paths,
|
|
117
|
+
or window titles.
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
macos-harness telemetry disable
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Experimental. macOS only. [MIT licensed](LICENSE).
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
<img src="static/banner.png" alt="macOS Harness" width="100%" />
|
|
2
|
+
|
|
3
|
+
# macOS Harness ⌘
|
|
4
|
+
|
|
5
|
+
The simplest, thinnest harness that gives an LLM complete freedom to complete
|
|
6
|
+
virtually any task on a Mac.
|
|
7
|
+
|
|
8
|
+
The agent writes what is missing, mid-task. No framework, no recipes, no rails.
|
|
9
|
+
One Python process connected directly to macOS, your real browser, and your files.
|
|
10
|
+
|
|
11
|
+
```text
|
|
12
|
+
● agent: wants to do something no helper exists for
|
|
13
|
+
│
|
|
14
|
+
● sees the app and uses raw macOS primitives
|
|
15
|
+
│
|
|
16
|
+
● writes the missing logic in ordinary Python
|
|
17
|
+
│
|
|
18
|
+
✓ task complete no app-specific tool added
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
**Your agent now has a Mac.**
|
|
22
|
+
|
|
23
|
+
## Give it to your agent
|
|
24
|
+
|
|
25
|
+
Paste this into Codex or Claude Code:
|
|
26
|
+
|
|
27
|
+
```text
|
|
28
|
+
Install or upgrade macOS Harness from https://github.com/browser-use/macos-harness with uv using Python 3.12. Register the skill printed by `macos-harness skill`, then run `macos-harness doctor`. Explain any missing macOS permissions and ask before requesting them. Finally, verify the harness by capturing one already-running app without bringing it to the foreground.
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
That is it. The agent installs the package, teaches itself the workflow, checks
|
|
32
|
+
permissions, and verifies the connection. [Manual setup](install.md) is available too.
|
|
33
|
+
|
|
34
|
+
## Six primitives. The whole Mac.
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
macos-harness <<'PY'
|
|
38
|
+
frame = mac.see("Spotify")
|
|
39
|
+
mac.key("cmd+k", app="Spotify")
|
|
40
|
+
mac.type("Alessia Cara", app="Spotify")
|
|
41
|
+
mac.click(640, 420, app="Spotify")
|
|
42
|
+
|
|
43
|
+
item = mac.ax.at(640, 420, app="Spotify")
|
|
44
|
+
mac.script('tell application "Spotify" to play')
|
|
45
|
+
|
|
46
|
+
print(browser.page_info())
|
|
47
|
+
print(list(Path.home().iterdir()))
|
|
48
|
+
PY
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Think in `see`, `key`, `type`, `click`, `ax`, and `script`. `browser`, `Path`, and
|
|
52
|
+
`subprocess` are ready in the same Python process.
|
|
53
|
+
|
|
54
|
+
There are no Spotify tools, Slack tools, or Final Cut tools. The model gets raw
|
|
55
|
+
primitives and writes the rest.
|
|
56
|
+
|
|
57
|
+
## How it works
|
|
58
|
+
|
|
59
|
+
```text
|
|
60
|
+
one persistent Python process
|
|
61
|
+
│
|
|
62
|
+
┌──────────────────────┼──────────────────────┐
|
|
63
|
+
│ │ │
|
|
64
|
+
mac.* browser.* Path / subprocess
|
|
65
|
+
│ │ │
|
|
66
|
+
┌───────────┼───────────┐ Browser Harness files + shell
|
|
67
|
+
│ │ │ │
|
|
68
|
+
CGWindow CGEvent AX + Apple CDP
|
|
69
|
+
screenshots to PID Events │
|
|
70
|
+
│ │ │ real Chrome
|
|
71
|
+
└───────────┴───────────┘
|
|
72
|
+
│
|
|
73
|
+
native + Electron apps
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
- Captures background app windows without bringing them forward
|
|
77
|
+
- Sends keyboard and coordinate input directly to an app PID
|
|
78
|
+
- Draws an animated, click-through pointer without moving your real cursor
|
|
79
|
+
- Exposes raw Apple Accessibility and Apple Events when vision is not enough
|
|
80
|
+
- Uses Browser Harness for the real, logged-in browser
|
|
81
|
+
- Keeps ordinary Python and the local filesystem within reach
|
|
82
|
+
|
|
83
|
+
## Permissions and privacy
|
|
84
|
+
|
|
85
|
+
`macos-harness doctor` reports the macOS permissions actually needed. The harness
|
|
86
|
+
never activates or raises a target app and never moves the physical pointer.
|
|
87
|
+
|
|
88
|
+
Anonymous telemetry is enabled by default. It records only the CLI command
|
|
89
|
+
category, success, duration, package version, OS/architecture, and detected agent
|
|
90
|
+
client. It never records prompts, app names, screenshots, UI text, scripts, paths,
|
|
91
|
+
or window titles.
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
macos-harness telemetry disable
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Experimental. macOS only. [MIT licensed](LICENSE).
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Security
|
|
2
|
+
|
|
3
|
+
Please report vulnerabilities privately through GitHub Security Advisories for
|
|
4
|
+
`browser-use/macos-harness`.
|
|
5
|
+
|
|
6
|
+
macOS Harness can control applications and read visible UI content with the
|
|
7
|
+
permissions the user grants it. Treat agent instructions and third-party UI as
|
|
8
|
+
untrusted input, review irreversible actions, and grant the minimum permissions
|
|
9
|
+
needed for the task.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Install macOS Harness
|
|
2
|
+
|
|
3
|
+
## Let your agent do it
|
|
4
|
+
|
|
5
|
+
Paste this into Codex or Claude Code:
|
|
6
|
+
|
|
7
|
+
```text
|
|
8
|
+
Install or upgrade macOS Harness from https://github.com/browser-use/macos-harness with uv using Python 3.12. Register the skill printed by `macos-harness skill`, then run `macos-harness doctor`. Explain any missing macOS permissions and ask before requesting them. Finally, verify the harness by capturing one already-running app without bringing it to the foreground.
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Or install it yourself
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
uv tool install --python 3.12 --upgrade --force macos-harness
|
|
15
|
+
mkdir -p "${CODEX_HOME:-$HOME/.codex}/skills/macos-harness"
|
|
16
|
+
macos-harness skill > "${CODEX_HOME:-$HOME/.codex}/skills/macos-harness/SKILL.md"
|
|
17
|
+
macos-harness doctor
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Grant only the permissions reported by `doctor`. macOS may request Accessibility,
|
|
21
|
+
Screen Recording, and Automation access. Input Monitoring is not required.
|
|
22
|
+
|
|
23
|
+
Verify without focusing the target app:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
macos-harness <<'PY'
|
|
27
|
+
print(mac.see("Finder"))
|
|
28
|
+
PY
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Anonymous telemetry contains only the CLI command category, success, duration,
|
|
32
|
+
package version, OS/architecture, and detected agent client. It never includes
|
|
33
|
+
prompts, app names, screenshots, text, scripts, paths, or window titles.
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
macos-harness telemetry disable
|
|
37
|
+
```
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
ROOT="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
|
5
|
+
|
|
6
|
+
if [ -x "$ROOT/.venv/bin/python" ]; then
|
|
7
|
+
exec "$ROOT/.venv/bin/python" -m macos_harness.cli "$@"
|
|
8
|
+
fi
|
|
9
|
+
|
|
10
|
+
exec uv --directory "$ROOT" run python -m macos_harness.cli "$@"
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.27"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "macos-harness"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "The simplest, thinnest harness that gives an LLM complete freedom to control a Mac."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
keywords = ["agent", "automation", "computer-use", "macos", "accessibility", "cdp"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 3 - Alpha",
|
|
16
|
+
"Environment :: MacOS X",
|
|
17
|
+
"Environment :: Console",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"Operating System :: MacOS",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Topic :: Desktop Environment",
|
|
24
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
25
|
+
]
|
|
26
|
+
dependencies = [
|
|
27
|
+
"browser-harness>=0.1.9",
|
|
28
|
+
"pillow>=11.3",
|
|
29
|
+
"pyobjc-framework-ApplicationServices>=12.0; sys_platform == 'darwin'",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
[dependency-groups]
|
|
33
|
+
dev = [
|
|
34
|
+
"pytest>=8.4",
|
|
35
|
+
"pyyaml>=6.0",
|
|
36
|
+
"ruff>=0.12",
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
[project.scripts]
|
|
40
|
+
macos-harness = "macos_harness.cli:main"
|
|
41
|
+
|
|
42
|
+
[project.urls]
|
|
43
|
+
Homepage = "https://github.com/browser-use/macos-harness"
|
|
44
|
+
Repository = "https://github.com/browser-use/macos-harness"
|
|
45
|
+
Issues = "https://github.com/browser-use/macos-harness/issues"
|
|
46
|
+
|
|
47
|
+
[tool.hatch.build.targets.wheel]
|
|
48
|
+
packages = ["src/macos_harness"]
|
|
49
|
+
|
|
50
|
+
[tool.hatch.build.targets.wheel.force-include]
|
|
51
|
+
"skills/macos-harness/SKILL.md" = "macos_harness/SKILL.md"
|
|
52
|
+
|
|
53
|
+
[tool.pytest.ini_options]
|
|
54
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: macos-harness
|
|
3
|
+
description: Control a whole Mac from one persistent Python session with screenshots, PID-targeted input, an animated virtual pointer, targeted Apple Accessibility, Apple Events, Browser Harness CDP, and filesystem access. Use for native, Electron, browser, dialog, file, or cross-app tasks without moving the physical cursor or forcing apps into the foreground.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# macOS Harness
|
|
7
|
+
|
|
8
|
+
Use one CLI call per decision point, not per primitive:
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
macos-harness <<'PY'
|
|
12
|
+
app = "Spotify"
|
|
13
|
+
mac.see(app)
|
|
14
|
+
mac.key("cmd+k", app=app)
|
|
15
|
+
mac.type("Alessia Cara", app=app)
|
|
16
|
+
print(mac.see(app))
|
|
17
|
+
PY
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
The CLI preloads `mac`, `browser`, `Path`, and `subprocess`. Prefer bounded stdin
|
|
21
|
+
programs; reserve `macos-harness repl` for manual exploration and always exit it.
|
|
22
|
+
|
|
23
|
+
## Minimize round trips
|
|
24
|
+
|
|
25
|
+
- Bundle deterministic, reversible steps into one program, then verify once. Opening
|
|
26
|
+
search, typing a query, and capturing the results is one burst—not three calls.
|
|
27
|
+
- Stop at a genuine decision boundary: ambiguous identity, new coordinates, an
|
|
28
|
+
irreversible action, or unexpected state. Inspect once, then run the next burst.
|
|
29
|
+
- Do not screenshot merely to confirm that a known shortcut opened a text field
|
|
30
|
+
before typing. Let the final screenshot verify the whole sequence.
|
|
31
|
+
- Poll exact AX or Apple Events state inside the same Python program when possible;
|
|
32
|
+
do not make the LLM repeatedly ask whether a transition finished.
|
|
33
|
+
- Use the cheapest strong end-state check. Prefer one screenshot for visible state
|
|
34
|
+
or one exact API/AX query for semantic state; use both only when they prove
|
|
35
|
+
different things.
|
|
36
|
+
|
|
37
|
+
## Use the small surface
|
|
38
|
+
|
|
39
|
+
Think in six verbs: `see`, `key`, `type`, `click`, `ax`, `script`.
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
frame = mac.see("Spotify")
|
|
43
|
+
mac.key("cmd+k", app="Spotify")
|
|
44
|
+
mac.type("Alessia Cara", app="Spotify")
|
|
45
|
+
mac.click(640, 420, app="Spotify")
|
|
46
|
+
|
|
47
|
+
item = mac.ax.at(640, 420, app="Spotify")
|
|
48
|
+
mac.ax.perform(item["element_index"], "AXPress")
|
|
49
|
+
|
|
50
|
+
mac.script('tell application "Spotify" to play')
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Use ordinary Python for local context and one-off logic. Do not add app-specific
|
|
54
|
+
helpers when a short program can resolve the task.
|
|
55
|
+
|
|
56
|
+
## Choose the lowest useful mode
|
|
57
|
+
|
|
58
|
+
1. When identity depends on local context (`my`, `friend`, or prior activity),
|
|
59
|
+
inspect that context and correlate stable fields; a loose text hit is not enough.
|
|
60
|
+
2. Use `mac.script()` for a known exact, focus-safe app command.
|
|
61
|
+
3. Otherwise use `mac.see(app)` and vision.
|
|
62
|
+
4. Prefer a known keyboard route; use a verified coordinate for a visible,
|
|
63
|
+
low-risk target.
|
|
64
|
+
5. Use targeted `mac.ax` only when semantic identity or state matters. Do not dump
|
|
65
|
+
a full AX tree before trying the direct route.
|
|
66
|
+
|
|
67
|
+
After a failed verified burst, switch mode or stop. Never repair uncertainty with
|
|
68
|
+
repeated keys, clicks, deletion loops, or bulk input.
|
|
69
|
+
|
|
70
|
+
## Keep the invariants
|
|
71
|
+
|
|
72
|
+
- Input targets an already-running app PID and never requests activation or raise.
|
|
73
|
+
- A background target becoming frontmost raises `FocusChangedError`; never
|
|
74
|
+
manipulate focus to restore it.
|
|
75
|
+
- `mac.click()` is raw PID-targeted input. It never guesses an AX action.
|
|
76
|
+
- The animated pointer is click-through and never moves the physical cursor.
|
|
77
|
+
- `mac.move()` moves only that pointer; it cannot produce native hover.
|
|
78
|
+
- Inactive apps may reject raw clicks. After one verified failure, switch mode.
|
|
79
|
+
- Never launch a closed app or use a custom URL scheme when focus is forbidden.
|
|
80
|
+
- Screenshot coordinates come from the latest `mac.see()` and preserve window
|
|
81
|
+
bounds and Retina scaling.
|
|
82
|
+
|
|
83
|
+
Secondary primitives are `mac.move`, `drag`, `scroll`, `show_pointer`, and
|
|
84
|
+
`hide_pointer`. `mac.ax.query()` returns compact matches and bounds fallback
|
|
85
|
+
traversal; lower `max_nodes` for especially large apps.
|
|
86
|
+
|
|
87
|
+
## Browser and permissions
|
|
88
|
+
|
|
89
|
+
Use `browser` for DOM, tabs, network, downloads, and uploads. Do not substitute AX
|
|
90
|
+
for CDP inside a web page. While Browser Harness connects, macOS Harness accepts
|
|
91
|
+
Chrome's exact `Allow remote debugging?` sheet through system-wide AX. It never
|
|
92
|
+
activates Chrome or emits a mouse event.
|
|
93
|
+
|
|
94
|
+
Run `macos-harness doctor` to inspect permissions without prompting. Run
|
|
95
|
+
`macos-harness doctor --request` only with user approval. Accessibility, screen
|
|
96
|
+
recording, and event posting are global; Apple Events Automation is per target.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Public Python interface for macOS Harness."""
|
|
2
|
+
|
|
3
|
+
from .browser import BrowserHarness
|
|
4
|
+
from .macos import AccessibilityPermissionError, FocusChangedError, MacOS, MacOSError
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
"AccessibilityPermissionError",
|
|
8
|
+
"BrowserHarness",
|
|
9
|
+
"FocusChangedError",
|
|
10
|
+
"MacOS",
|
|
11
|
+
"MacOSError",
|
|
12
|
+
]
|