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.
@@ -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,10 @@
1
+ .venv/
2
+ .pytest_cache/
3
+ __pycache__/
4
+ *.py[cod]
5
+ dist/
6
+ tmp/
7
+
8
+ # Local task artifacts can contain private workspace data.
9
+ slack-*.json
10
+ slack-*.md
@@ -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,4 @@
1
+ interface:
2
+ display_name: "macOS Harness"
3
+ short_description: "Simple visual control for the whole Mac"
4
+ default_prompt: "Use $macos-harness to see and control my Mac without moving my physical cursor or taking focus."
@@ -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
+ ]