opendesk 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.
Files changed (40) hide show
  1. opendesk-0.1.0/LICENSE +21 -0
  2. opendesk-0.1.0/PKG-INFO +364 -0
  3. opendesk-0.1.0/README.md +318 -0
  4. opendesk-0.1.0/opendesk/__init__.py +58 -0
  5. opendesk-0.1.0/opendesk/cli.py +90 -0
  6. opendesk-0.1.0/opendesk/computer/__init__.py +1 -0
  7. opendesk-0.1.0/opendesk/computer/capture.py +191 -0
  8. opendesk-0.1.0/opendesk/computer/deps.py +27 -0
  9. opendesk-0.1.0/opendesk/computer/marks.py +415 -0
  10. opendesk-0.1.0/opendesk/computer/ocr.py +159 -0
  11. opendesk-0.1.0/opendesk/computer/sandbox.py +188 -0
  12. opendesk-0.1.0/opendesk/integrations/__init__.py +1 -0
  13. opendesk-0.1.0/opendesk/integrations/claude_code.py +253 -0
  14. opendesk-0.1.0/opendesk/integrations/langchain_compat.py +106 -0
  15. opendesk-0.1.0/opendesk/integrations/mcp.py +173 -0
  16. opendesk-0.1.0/opendesk/integrations/openai_compat.py +201 -0
  17. opendesk-0.1.0/opendesk/learn/__init__.py +19 -0
  18. opendesk-0.1.0/opendesk/learn/recorder.py +366 -0
  19. opendesk-0.1.0/opendesk/learn/storage.py +67 -0
  20. opendesk-0.1.0/opendesk/learn/trajectory.py +201 -0
  21. opendesk-0.1.0/opendesk/registry.py +110 -0
  22. opendesk-0.1.0/opendesk/tools/__init__.py +19 -0
  23. opendesk-0.1.0/opendesk/tools/app.py +219 -0
  24. opendesk-0.1.0/opendesk/tools/base.py +239 -0
  25. opendesk-0.1.0/opendesk/tools/clipboard.py +137 -0
  26. opendesk-0.1.0/opendesk/tools/keyboard.py +208 -0
  27. opendesk-0.1.0/opendesk/tools/learn.py +194 -0
  28. opendesk-0.1.0/opendesk/tools/mouse.py +254 -0
  29. opendesk-0.1.0/opendesk/tools/ocr.py +74 -0
  30. opendesk-0.1.0/opendesk/tools/screenshot.py +237 -0
  31. opendesk-0.1.0/opendesk/tools/ui.py +727 -0
  32. opendesk-0.1.0/opendesk.egg-info/PKG-INFO +364 -0
  33. opendesk-0.1.0/opendesk.egg-info/SOURCES.txt +38 -0
  34. opendesk-0.1.0/opendesk.egg-info/dependency_links.txt +1 -0
  35. opendesk-0.1.0/opendesk.egg-info/entry_points.txt +3 -0
  36. opendesk-0.1.0/opendesk.egg-info/requires.txt +32 -0
  37. opendesk-0.1.0/opendesk.egg-info/top_level.txt +1 -0
  38. opendesk-0.1.0/pyproject.toml +94 -0
  39. opendesk-0.1.0/setup.cfg +4 -0
  40. opendesk-0.1.0/tests/test_base.py +195 -0
opendesk-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 vitalops.ai
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,364 @@
1
+ Metadata-Version: 2.4
2
+ Name: opendesk
3
+ Version: 0.1.0
4
+ Summary: Open Computer Use Agent — framework for desktop and browser automation
5
+ License: MIT
6
+ Project-URL: Homepage, https://github.com/abhijithneilabraham/opendesk
7
+ Project-URL: Documentation, https://github.com/abhijithneilabraham/opendesk/tree/main/docs
8
+ Project-URL: Issues, https://github.com/abhijithneilabraham/opendesk/issues
9
+ Keywords: computer-use,automation,accessibility,ai,agent,mcp
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Topic :: Software Development :: Libraries
18
+ Classifier: Topic :: System :: Operating System
19
+ Requires-Python: >=3.9
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Requires-Dist: pydantic>=2.0
23
+ Provides-Extra: core
24
+ Requires-Dist: mss>=9.0; extra == "core"
25
+ Requires-Dist: Pillow>=10.0; extra == "core"
26
+ Requires-Dist: pyautogui>=0.9; extra == "core"
27
+ Provides-Extra: mcp
28
+ Requires-Dist: mcp>=1.0; extra == "mcp"
29
+ Provides-Extra: openai
30
+ Requires-Dist: openai>=1.0; extra == "openai"
31
+ Provides-Extra: langchain
32
+ Requires-Dist: langchain-core>=0.2; extra == "langchain"
33
+ Provides-Extra: linux
34
+ Requires-Dist: pyatspi; extra == "linux"
35
+ Provides-Extra: windows
36
+ Requires-Dist: pywinauto; extra == "windows"
37
+ Requires-Dist: pygetwindow; extra == "windows"
38
+ Requires-Dist: pyperclip; extra == "windows"
39
+ Provides-Extra: ocr
40
+ Requires-Dist: pytesseract; extra == "ocr"
41
+ Provides-Extra: learn
42
+ Requires-Dist: pynput; extra == "learn"
43
+ Provides-Extra: all
44
+ Requires-Dist: opendesk[core,langchain,learn,mcp,ocr,openai]; extra == "all"
45
+ Dynamic: license-file
46
+
47
+ # opendesk
48
+
49
+ **Open Computer Use Agent** — gives any AI agent eyes and hands on your desktop.
50
+
51
+ opendesk runs as an MCP server. Install it, register it with your agent tool, and it adds screenshot, accessibility-based UI control, mouse, keyboard, clipboard, and OCR to every conversation — on macOS, Linux, and Windows.
52
+
53
+ ---
54
+
55
+ ## Quickstart
56
+
57
+ ```bash
58
+ pip install 'opendesk[core,mcp]'
59
+ ```
60
+
61
+ **Claude Code** — one command:
62
+ ```bash
63
+ claude mcp add opendesk -- opendesk-mcp
64
+ ```
65
+
66
+ **Claude Desktop** — add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
67
+ ```json
68
+ {
69
+ "mcpServers": {
70
+ "opendesk": { "command": "opendesk-mcp" }
71
+ }
72
+ }
73
+ ```
74
+
75
+ **Cursor / Continue** — same pattern, point `command` at `opendesk-mcp`.
76
+
77
+ That's it. Your agent can now say "take a screenshot", "click the Save button", or "type Hello World into TextEdit" and it will work.
78
+
79
+ ---
80
+
81
+ ## What it adds to your agent
82
+
83
+ | Tool | What it does |
84
+ |------|-------------|
85
+ | `ui` | Clicks buttons, types text, reads values — by element name, no pixel coordinates. Uses the platform's native accessibility tree (AppleScript / AT-SPI2 / UI Automation). |
86
+ | `screenshot` | Captures the screen. With `marks=True`, overlays numbered boxes on every interactive element so the agent can say "click mark 3". |
87
+ | `mouse` | Pixel-level mouse control with automatic Retina/HiDPI scaling. Last resort when `ui` has nothing to click. |
88
+ | `keyboard` | Types text (full Unicode), presses keys, sends hotkeys. |
89
+ | `app` | Opens, closes, and focuses applications. |
90
+ | `clipboard` | Reads and writes the system clipboard. |
91
+ | `ocr` | Extracts text from any screen region without sending to the LLM. |
92
+
93
+ **The agent follows a natural priority:** `ui` first (no coordinates needed) → `screenshot(marks=True)` to see numbered elements → `mouse` as last resort for unlabelled canvas areas.
94
+
95
+ ---
96
+
97
+ ## How the MCP integration works
98
+
99
+ ```
100
+ Claude Code / Claude Desktop / Cursor / Continue
101
+ |
102
+ | MCP stdio
103
+ v
104
+ opendesk-mcp
105
+ |
106
+ +-- screenshot, ui, mouse, keyboard, app, clipboard, ocr
107
+ ```
108
+
109
+ opendesk starts as a child process, speaks the MCP protocol over stdin/stdout, and the LLM client handles all tool-calling automatically. You never write tool-calling code.
110
+
111
+ ---
112
+
113
+ ## Why opendesk?
114
+
115
+ - **MCP-first** — works out of the box with any MCP client, zero glue code.
116
+ - **Accessibility tree first** — the `ui` tool interacts with apps the same way a screen reader does, without pixel coordinates or Retina scaling headaches.
117
+ - **Framework-agnostic** — also ships Anthropic SDK, OpenAI, and LangChain adapters.
118
+ - **Sandboxed** — per-session audit log, app allow-list, screen region constraints.
119
+ - **Extensible** — one class to add a custom tool; it appears in all integrations automatically.
120
+
121
+ ---
122
+
123
+ ## Installation
124
+
125
+ ```bash
126
+ # Minimal (just the framework, no hardware deps)
127
+ pip install opendesk
128
+
129
+ # Core computer use: screen capture + mouse/keyboard
130
+ pip install 'opendesk[core]'
131
+
132
+ # With MCP server support
133
+ pip install 'opendesk[core,mcp]'
134
+
135
+ # Everything
136
+ pip install 'opendesk[all]'
137
+ ```
138
+
139
+ ### System dependencies
140
+
141
+ | Platform | Required |
142
+ |----------|---------|
143
+ | macOS | Screen Recording permission (System Settings → Privacy → Screen Recording); Accessibility permission for mouse/keyboard |
144
+ | Linux | `xclip` for clipboard; `xdotool` or `pyatspi` for keyboard/UI |
145
+ | Windows | No extra system deps (uses Win32 APIs) |
146
+
147
+ ---
148
+
149
+ ## Quick start
150
+
151
+ ```python
152
+ import asyncio
153
+ from opendesk import create_registry, allow_all_context
154
+
155
+ async def main():
156
+ registry = create_registry()
157
+ ctx = allow_all_context()
158
+
159
+ # Take a screenshot with Set-of-Marks overlay
160
+ screenshot = registry.get("screenshot")
161
+ result = await screenshot.execute(ctx, screenshot.Params(marks=True))
162
+ print(result.output) # lists all interactive elements as [1] Button "OK" ...
163
+ # result.attachments[0].content -> PNG bytes
164
+
165
+ # Click a button by name — no pixel coordinates needed
166
+ ui = registry.get("ui")
167
+ await ui.execute(ctx, ui.Params(action="click", app="Safari", title="Go"))
168
+
169
+ # Type text
170
+ kb = registry.get("keyboard")
171
+ await kb.execute(ctx, kb.Params(action="type", text="hello world"))
172
+
173
+ asyncio.run(main())
174
+ ```
175
+
176
+ ---
177
+
178
+ ## Integrations
179
+
180
+ ### MCP server (Claude Desktop, Continue, Cursor, ...)
181
+
182
+ Run the MCP server over stdio:
183
+
184
+ ```bash
185
+ opendesk-mcp
186
+ ```
187
+
188
+ Add to Claude Desktop (`~/Library/Application Support/Claude/claude_desktop_config.json`):
189
+
190
+ ```json
191
+ {
192
+ "mcpServers": {
193
+ "opendesk": {
194
+ "command": "opendesk-mcp"
195
+ }
196
+ }
197
+ }
198
+ ```
199
+
200
+ Or create a server in Python:
201
+
202
+ ```python
203
+ from opendesk.integrations.mcp import create_mcp_server
204
+ from opendesk.registry import create_registry
205
+ from mcp.server.stdio import stdio_server
206
+
207
+ server = create_mcp_server(create_registry())
208
+ async with stdio_server() as (r, w):
209
+ await server.run(r, w, server.create_initialization_options())
210
+ ```
211
+
212
+ ---
213
+
214
+ ### Claude Code / Anthropic SDK
215
+
216
+ ```python
217
+ import anthropic
218
+ from opendesk.integrations.claude_code import ClaudeCodeAdapter
219
+ from opendesk.registry import create_registry
220
+
221
+ client = anthropic.Anthropic()
222
+ adapter = ClaudeCodeAdapter(create_registry())
223
+
224
+ messages = [{"role": "user", "content": "Open Safari and take a screenshot"}]
225
+
226
+ # Full agentic loop (handles tool use automatically)
227
+ final_text = await adapter.run_loop(
228
+ client,
229
+ model="claude-opus-4-6",
230
+ messages=messages,
231
+ system="You are a computer use agent. Use the ui tool first, mouse as last resort.",
232
+ )
233
+ print(final_text)
234
+ ```
235
+
236
+ Manual control:
237
+
238
+ ```python
239
+ response = client.messages.create(
240
+ model="claude-opus-4-6",
241
+ max_tokens=4096,
242
+ tools=adapter.tool_definitions(),
243
+ messages=messages,
244
+ )
245
+
246
+ # Dispatch all tool_use blocks in parallel
247
+ tool_results = await adapter.handle_response(response)
248
+ messages.append({"role": "assistant", "content": response.content})
249
+ messages.append({"role": "user", "content": tool_results})
250
+ ```
251
+
252
+ ---
253
+
254
+ ### OpenAI function calling
255
+
256
+ Works with OpenAI, Groq, Together AI, Ollama, LiteLLM, and any OpenAI-compatible provider:
257
+
258
+ ```python
259
+ from openai import OpenAI
260
+ from opendesk.integrations.openai_compat import OpenAIAdapter
261
+ from opendesk.registry import create_registry
262
+
263
+ client = OpenAI()
264
+ adapter = OpenAIAdapter(create_registry())
265
+
266
+ messages = [{"role": "user", "content": "Take a screenshot"}]
267
+ final_text = await adapter.run_loop(client, model="gpt-4o", messages=messages)
268
+ ```
269
+
270
+ ---
271
+
272
+ ### LangChain / LangGraph
273
+
274
+ ```python
275
+ from langchain_openai import ChatOpenAI
276
+ from langgraph.prebuilt import create_react_agent
277
+
278
+ from opendesk.integrations.langchain_compat import as_langchain_tools
279
+ from opendesk.registry import create_registry
280
+
281
+ tools = as_langchain_tools(create_registry())
282
+ agent = create_react_agent(ChatOpenAI(model="gpt-4o"), tools)
283
+ result = agent.invoke({"messages": [("user", "Take a screenshot")]})
284
+ ```
285
+
286
+ ---
287
+
288
+ ## Tools
289
+
290
+ | Tool | Description |
291
+ |------|-------------|
292
+ | `ui` | Click, type, and read UI elements by name via the accessibility tree. **Use this first.** |
293
+ | `screenshot` | Capture screen with Set-of-Marks overlay, cursor dot, zoom, and change detection |
294
+ | `mouse` | Click, scroll, drag — with automatic Retina/HiDPI coordinate scaling |
295
+ | `keyboard` | Type (full Unicode), press keys, hotkeys, hold |
296
+ | `app` | Open, close, focus, or list applications |
297
+ | `clipboard` | Read or write the system clipboard |
298
+ | `ocr` | Extract text from any screen region (pytesseract / Vision / WinRT) |
299
+
300
+ ### Tool priority
301
+
302
+ When the agent needs to interact with a UI element:
303
+
304
+ 1. **`ui` tool** — click by element title/role, no coordinates needed. Most reliable.
305
+ 2. **`screenshot` with `marks=True`** — if `ui` doesn't find the element, get a SoM overlay showing numbered bounding boxes.
306
+ 3. **`mouse` with `image_width`/`image_height`** — last resort for unlabelled canvas areas. Always provide image dimensions for correct Retina scaling.
307
+
308
+ ---
309
+
310
+ ## Architecture
311
+
312
+ ```
313
+ opendesk/
314
+ ├── tools/ # Tool definitions (base.py + one file per tool)
315
+ ├── computer/ # Low-level helpers: capture, marks (SoM), OCR, sandbox
316
+ ├── integrations/ # MCP, Claude Code, OpenAI, LangChain adapters
317
+ └── registry.py # ToolRegistry + create_registry()
318
+ ```
319
+
320
+ See [docs/architecture.md](docs/architecture.md) for a deep dive.
321
+
322
+ ---
323
+
324
+ ## Permission model
325
+
326
+ Every tool action goes through a `ToolContext.check_permission()` call before execution.
327
+
328
+ ```python
329
+ from opendesk.tools.base import allow_all_context, interactive_context
330
+
331
+ # Headless / autonomous — approve everything automatically
332
+ ctx = allow_all_context()
333
+
334
+ # Interactive — prompt on stdout before each action
335
+ ctx = interactive_context()
336
+
337
+ # Custom handler — integrate with your own UI or policy engine
338
+ async def my_handler(tool: str, argument: str, description: str) -> None:
339
+ if "production" in description.lower():
340
+ raise PermissionDeniedError("Refusing to act on production.")
341
+
342
+ from opendesk.tools.base import ToolContext
343
+ ctx = ToolContext(session_id="my-session", permission_handler=my_handler)
344
+ ```
345
+
346
+ ---
347
+
348
+ ## Platform support
349
+
350
+ | Feature | macOS | Linux | Windows |
351
+ |---------|-------|-------|---------|
352
+ | Screenshot | mss + Pillow | mss + Pillow | mss + Pillow |
353
+ | Mouse control | pyautogui | pyautogui | pyautogui |
354
+ | Keyboard (Unicode) | pbcopy + cmd+v | xclip/xsel + ctrl+v | pyperclip + ctrl+v |
355
+ | AX tree (`ui` tool) | AppleScript | AT-SPI2 / xdotool | pywinauto |
356
+ | SoM marks | AppleScript | pyatspi | pywinauto |
357
+ | OCR | pytesseract / Vision | pytesseract | pytesseract / WinRT |
358
+ | App open/close | `open -a` / AppleScript | xdg-open / pkill | start / taskkill |
359
+
360
+ ---
361
+
362
+ ## License
363
+
364
+ MIT
@@ -0,0 +1,318 @@
1
+ # opendesk
2
+
3
+ **Open Computer Use Agent** — gives any AI agent eyes and hands on your desktop.
4
+
5
+ opendesk runs as an MCP server. Install it, register it with your agent tool, and it adds screenshot, accessibility-based UI control, mouse, keyboard, clipboard, and OCR to every conversation — on macOS, Linux, and Windows.
6
+
7
+ ---
8
+
9
+ ## Quickstart
10
+
11
+ ```bash
12
+ pip install 'opendesk[core,mcp]'
13
+ ```
14
+
15
+ **Claude Code** — one command:
16
+ ```bash
17
+ claude mcp add opendesk -- opendesk-mcp
18
+ ```
19
+
20
+ **Claude Desktop** — add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
21
+ ```json
22
+ {
23
+ "mcpServers": {
24
+ "opendesk": { "command": "opendesk-mcp" }
25
+ }
26
+ }
27
+ ```
28
+
29
+ **Cursor / Continue** — same pattern, point `command` at `opendesk-mcp`.
30
+
31
+ That's it. Your agent can now say "take a screenshot", "click the Save button", or "type Hello World into TextEdit" and it will work.
32
+
33
+ ---
34
+
35
+ ## What it adds to your agent
36
+
37
+ | Tool | What it does |
38
+ |------|-------------|
39
+ | `ui` | Clicks buttons, types text, reads values — by element name, no pixel coordinates. Uses the platform's native accessibility tree (AppleScript / AT-SPI2 / UI Automation). |
40
+ | `screenshot` | Captures the screen. With `marks=True`, overlays numbered boxes on every interactive element so the agent can say "click mark 3". |
41
+ | `mouse` | Pixel-level mouse control with automatic Retina/HiDPI scaling. Last resort when `ui` has nothing to click. |
42
+ | `keyboard` | Types text (full Unicode), presses keys, sends hotkeys. |
43
+ | `app` | Opens, closes, and focuses applications. |
44
+ | `clipboard` | Reads and writes the system clipboard. |
45
+ | `ocr` | Extracts text from any screen region without sending to the LLM. |
46
+
47
+ **The agent follows a natural priority:** `ui` first (no coordinates needed) → `screenshot(marks=True)` to see numbered elements → `mouse` as last resort for unlabelled canvas areas.
48
+
49
+ ---
50
+
51
+ ## How the MCP integration works
52
+
53
+ ```
54
+ Claude Code / Claude Desktop / Cursor / Continue
55
+ |
56
+ | MCP stdio
57
+ v
58
+ opendesk-mcp
59
+ |
60
+ +-- screenshot, ui, mouse, keyboard, app, clipboard, ocr
61
+ ```
62
+
63
+ opendesk starts as a child process, speaks the MCP protocol over stdin/stdout, and the LLM client handles all tool-calling automatically. You never write tool-calling code.
64
+
65
+ ---
66
+
67
+ ## Why opendesk?
68
+
69
+ - **MCP-first** — works out of the box with any MCP client, zero glue code.
70
+ - **Accessibility tree first** — the `ui` tool interacts with apps the same way a screen reader does, without pixel coordinates or Retina scaling headaches.
71
+ - **Framework-agnostic** — also ships Anthropic SDK, OpenAI, and LangChain adapters.
72
+ - **Sandboxed** — per-session audit log, app allow-list, screen region constraints.
73
+ - **Extensible** — one class to add a custom tool; it appears in all integrations automatically.
74
+
75
+ ---
76
+
77
+ ## Installation
78
+
79
+ ```bash
80
+ # Minimal (just the framework, no hardware deps)
81
+ pip install opendesk
82
+
83
+ # Core computer use: screen capture + mouse/keyboard
84
+ pip install 'opendesk[core]'
85
+
86
+ # With MCP server support
87
+ pip install 'opendesk[core,mcp]'
88
+
89
+ # Everything
90
+ pip install 'opendesk[all]'
91
+ ```
92
+
93
+ ### System dependencies
94
+
95
+ | Platform | Required |
96
+ |----------|---------|
97
+ | macOS | Screen Recording permission (System Settings → Privacy → Screen Recording); Accessibility permission for mouse/keyboard |
98
+ | Linux | `xclip` for clipboard; `xdotool` or `pyatspi` for keyboard/UI |
99
+ | Windows | No extra system deps (uses Win32 APIs) |
100
+
101
+ ---
102
+
103
+ ## Quick start
104
+
105
+ ```python
106
+ import asyncio
107
+ from opendesk import create_registry, allow_all_context
108
+
109
+ async def main():
110
+ registry = create_registry()
111
+ ctx = allow_all_context()
112
+
113
+ # Take a screenshot with Set-of-Marks overlay
114
+ screenshot = registry.get("screenshot")
115
+ result = await screenshot.execute(ctx, screenshot.Params(marks=True))
116
+ print(result.output) # lists all interactive elements as [1] Button "OK" ...
117
+ # result.attachments[0].content -> PNG bytes
118
+
119
+ # Click a button by name — no pixel coordinates needed
120
+ ui = registry.get("ui")
121
+ await ui.execute(ctx, ui.Params(action="click", app="Safari", title="Go"))
122
+
123
+ # Type text
124
+ kb = registry.get("keyboard")
125
+ await kb.execute(ctx, kb.Params(action="type", text="hello world"))
126
+
127
+ asyncio.run(main())
128
+ ```
129
+
130
+ ---
131
+
132
+ ## Integrations
133
+
134
+ ### MCP server (Claude Desktop, Continue, Cursor, ...)
135
+
136
+ Run the MCP server over stdio:
137
+
138
+ ```bash
139
+ opendesk-mcp
140
+ ```
141
+
142
+ Add to Claude Desktop (`~/Library/Application Support/Claude/claude_desktop_config.json`):
143
+
144
+ ```json
145
+ {
146
+ "mcpServers": {
147
+ "opendesk": {
148
+ "command": "opendesk-mcp"
149
+ }
150
+ }
151
+ }
152
+ ```
153
+
154
+ Or create a server in Python:
155
+
156
+ ```python
157
+ from opendesk.integrations.mcp import create_mcp_server
158
+ from opendesk.registry import create_registry
159
+ from mcp.server.stdio import stdio_server
160
+
161
+ server = create_mcp_server(create_registry())
162
+ async with stdio_server() as (r, w):
163
+ await server.run(r, w, server.create_initialization_options())
164
+ ```
165
+
166
+ ---
167
+
168
+ ### Claude Code / Anthropic SDK
169
+
170
+ ```python
171
+ import anthropic
172
+ from opendesk.integrations.claude_code import ClaudeCodeAdapter
173
+ from opendesk.registry import create_registry
174
+
175
+ client = anthropic.Anthropic()
176
+ adapter = ClaudeCodeAdapter(create_registry())
177
+
178
+ messages = [{"role": "user", "content": "Open Safari and take a screenshot"}]
179
+
180
+ # Full agentic loop (handles tool use automatically)
181
+ final_text = await adapter.run_loop(
182
+ client,
183
+ model="claude-opus-4-6",
184
+ messages=messages,
185
+ system="You are a computer use agent. Use the ui tool first, mouse as last resort.",
186
+ )
187
+ print(final_text)
188
+ ```
189
+
190
+ Manual control:
191
+
192
+ ```python
193
+ response = client.messages.create(
194
+ model="claude-opus-4-6",
195
+ max_tokens=4096,
196
+ tools=adapter.tool_definitions(),
197
+ messages=messages,
198
+ )
199
+
200
+ # Dispatch all tool_use blocks in parallel
201
+ tool_results = await adapter.handle_response(response)
202
+ messages.append({"role": "assistant", "content": response.content})
203
+ messages.append({"role": "user", "content": tool_results})
204
+ ```
205
+
206
+ ---
207
+
208
+ ### OpenAI function calling
209
+
210
+ Works with OpenAI, Groq, Together AI, Ollama, LiteLLM, and any OpenAI-compatible provider:
211
+
212
+ ```python
213
+ from openai import OpenAI
214
+ from opendesk.integrations.openai_compat import OpenAIAdapter
215
+ from opendesk.registry import create_registry
216
+
217
+ client = OpenAI()
218
+ adapter = OpenAIAdapter(create_registry())
219
+
220
+ messages = [{"role": "user", "content": "Take a screenshot"}]
221
+ final_text = await adapter.run_loop(client, model="gpt-4o", messages=messages)
222
+ ```
223
+
224
+ ---
225
+
226
+ ### LangChain / LangGraph
227
+
228
+ ```python
229
+ from langchain_openai import ChatOpenAI
230
+ from langgraph.prebuilt import create_react_agent
231
+
232
+ from opendesk.integrations.langchain_compat import as_langchain_tools
233
+ from opendesk.registry import create_registry
234
+
235
+ tools = as_langchain_tools(create_registry())
236
+ agent = create_react_agent(ChatOpenAI(model="gpt-4o"), tools)
237
+ result = agent.invoke({"messages": [("user", "Take a screenshot")]})
238
+ ```
239
+
240
+ ---
241
+
242
+ ## Tools
243
+
244
+ | Tool | Description |
245
+ |------|-------------|
246
+ | `ui` | Click, type, and read UI elements by name via the accessibility tree. **Use this first.** |
247
+ | `screenshot` | Capture screen with Set-of-Marks overlay, cursor dot, zoom, and change detection |
248
+ | `mouse` | Click, scroll, drag — with automatic Retina/HiDPI coordinate scaling |
249
+ | `keyboard` | Type (full Unicode), press keys, hotkeys, hold |
250
+ | `app` | Open, close, focus, or list applications |
251
+ | `clipboard` | Read or write the system clipboard |
252
+ | `ocr` | Extract text from any screen region (pytesseract / Vision / WinRT) |
253
+
254
+ ### Tool priority
255
+
256
+ When the agent needs to interact with a UI element:
257
+
258
+ 1. **`ui` tool** — click by element title/role, no coordinates needed. Most reliable.
259
+ 2. **`screenshot` with `marks=True`** — if `ui` doesn't find the element, get a SoM overlay showing numbered bounding boxes.
260
+ 3. **`mouse` with `image_width`/`image_height`** — last resort for unlabelled canvas areas. Always provide image dimensions for correct Retina scaling.
261
+
262
+ ---
263
+
264
+ ## Architecture
265
+
266
+ ```
267
+ opendesk/
268
+ ├── tools/ # Tool definitions (base.py + one file per tool)
269
+ ├── computer/ # Low-level helpers: capture, marks (SoM), OCR, sandbox
270
+ ├── integrations/ # MCP, Claude Code, OpenAI, LangChain adapters
271
+ └── registry.py # ToolRegistry + create_registry()
272
+ ```
273
+
274
+ See [docs/architecture.md](docs/architecture.md) for a deep dive.
275
+
276
+ ---
277
+
278
+ ## Permission model
279
+
280
+ Every tool action goes through a `ToolContext.check_permission()` call before execution.
281
+
282
+ ```python
283
+ from opendesk.tools.base import allow_all_context, interactive_context
284
+
285
+ # Headless / autonomous — approve everything automatically
286
+ ctx = allow_all_context()
287
+
288
+ # Interactive — prompt on stdout before each action
289
+ ctx = interactive_context()
290
+
291
+ # Custom handler — integrate with your own UI or policy engine
292
+ async def my_handler(tool: str, argument: str, description: str) -> None:
293
+ if "production" in description.lower():
294
+ raise PermissionDeniedError("Refusing to act on production.")
295
+
296
+ from opendesk.tools.base import ToolContext
297
+ ctx = ToolContext(session_id="my-session", permission_handler=my_handler)
298
+ ```
299
+
300
+ ---
301
+
302
+ ## Platform support
303
+
304
+ | Feature | macOS | Linux | Windows |
305
+ |---------|-------|-------|---------|
306
+ | Screenshot | mss + Pillow | mss + Pillow | mss + Pillow |
307
+ | Mouse control | pyautogui | pyautogui | pyautogui |
308
+ | Keyboard (Unicode) | pbcopy + cmd+v | xclip/xsel + ctrl+v | pyperclip + ctrl+v |
309
+ | AX tree (`ui` tool) | AppleScript | AT-SPI2 / xdotool | pywinauto |
310
+ | SoM marks | AppleScript | pyatspi | pywinauto |
311
+ | OCR | pytesseract / Vision | pytesseract | pytesseract / WinRT |
312
+ | App open/close | `open -a` / AppleScript | xdg-open / pkill | start / taskkill |
313
+
314
+ ---
315
+
316
+ ## License
317
+
318
+ MIT