daytona-use-computer 0.0.48__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 (63) hide show
  1. daytona_use_computer-0.0.48/.env.example +6 -0
  2. daytona_use_computer-0.0.48/.gitignore +9 -0
  3. daytona_use_computer-0.0.48/.pre-commit-config.yaml +15 -0
  4. daytona_use_computer-0.0.48/PKG-INFO +197 -0
  5. daytona_use_computer-0.0.48/README.md +167 -0
  6. daytona_use_computer-0.0.48/pyproject.toml +82 -0
  7. daytona_use_computer-0.0.48/scripts/smoke_wheel.py +193 -0
  8. daytona_use_computer-0.0.48/use_computer/__init__.py +58 -0
  9. daytona_use_computer-0.0.48/use_computer/agents/__init__.py +52 -0
  10. daytona_use_computer-0.0.48/use_computer/agents/action_manifest.json +40 -0
  11. daytona_use_computer-0.0.48/use_computer/agents/base/__init__.py +31 -0
  12. daytona_use_computer-0.0.48/use_computer/agents/base/actions.py +282 -0
  13. daytona_use_computer-0.0.48/use_computer/agents/base/agent.py +275 -0
  14. daytona_use_computer-0.0.48/use_computer/agents/base/artifacts.py +90 -0
  15. daytona_use_computer-0.0.48/use_computer/agents/base/desktop.py +147 -0
  16. daytona_use_computer-0.0.48/use_computer/agents/base/final_state.py +136 -0
  17. daytona_use_computer-0.0.48/use_computer/agents/base/prompts.py +44 -0
  18. daytona_use_computer-0.0.48/use_computer/agents/base/recordings.py +162 -0
  19. daytona_use_computer-0.0.48/use_computer/agents/base/sandbox.py +7 -0
  20. daytona_use_computer-0.0.48/use_computer/agents/base/screenshots.py +52 -0
  21. daytona_use_computer-0.0.48/use_computer/agents/base/task_setup.py +273 -0
  22. daytona_use_computer-0.0.48/use_computer/agents/base/trajectory.py +34 -0
  23. daytona_use_computer-0.0.48/use_computer/agents/core.py +65 -0
  24. daytona_use_computer-0.0.48/use_computer/agents/debug/README.md +10 -0
  25. daytona_use_computer-0.0.48/use_computer/agents/debug/__init__.py +5 -0
  26. daytona_use_computer-0.0.48/use_computer/agents/debug/actions.py +89 -0
  27. daytona_use_computer-0.0.48/use_computer/agents/debug/agent.py +205 -0
  28. daytona_use_computer-0.0.48/use_computer/agents/debug/models.py +88 -0
  29. daytona_use_computer-0.0.48/use_computer/agents/prompts/anthropic.txt +7 -0
  30. daytona_use_computer-0.0.48/use_computer/agents/prompts/gemini.txt +6 -0
  31. daytona_use_computer-0.0.48/use_computer/agents/prompts/openai.txt +7 -0
  32. daytona_use_computer-0.0.48/use_computer/agents/prompts/osworld_pyautogui.txt +18 -0
  33. daytona_use_computer-0.0.48/use_computer/agents/prompts/pyautogui.txt +50 -0
  34. daytona_use_computer-0.0.48/use_computer/agents/providers/__init__.py +1 -0
  35. daytona_use_computer-0.0.48/use_computer/agents/providers/anthropic.py +316 -0
  36. daytona_use_computer-0.0.48/use_computer/agents/providers/gemini.py +413 -0
  37. daytona_use_computer-0.0.48/use_computer/agents/providers/generic.py +620 -0
  38. daytona_use_computer-0.0.48/use_computer/agents/providers/openai.py +363 -0
  39. daytona_use_computer-0.0.48/use_computer/agents/providers/osworld.py +229 -0
  40. daytona_use_computer-0.0.48/use_computer/agents/providers/tinker_backend.py +163 -0
  41. daytona_use_computer-0.0.48/use_computer/agents/types.py +204 -0
  42. daytona_use_computer-0.0.48/use_computer/automation/__init__.py +28 -0
  43. daytona_use_computer-0.0.48/use_computer/automation/ax_transpile.py +504 -0
  44. daytona_use_computer-0.0.48/use_computer/automation/osworld.py +466 -0
  45. daytona_use_computer-0.0.48/use_computer/automation/parsers.py +307 -0
  46. daytona_use_computer-0.0.48/use_computer/core/__init__.py +51 -0
  47. daytona_use_computer-0.0.48/use_computer/core/client.py +421 -0
  48. daytona_use_computer-0.0.48/use_computer/core/errors.py +112 -0
  49. daytona_use_computer-0.0.48/use_computer/core/models.py +132 -0
  50. daytona_use_computer-0.0.48/use_computer/core/retry.py +140 -0
  51. daytona_use_computer-0.0.48/use_computer/core/sandbox.py +514 -0
  52. daytona_use_computer-0.0.48/use_computer/macos/__init__.py +4 -0
  53. daytona_use_computer-0.0.48/use_computer/macos/keyboard.py +73 -0
  54. daytona_use_computer-0.0.48/use_computer/macos/mouse.py +207 -0
  55. daytona_use_computer-0.0.48/use_computer/py.typed +1 -0
  56. daytona_use_computer-0.0.48/use_computer/resources/__init__.py +20 -0
  57. daytona_use_computer-0.0.48/use_computer/resources/accessibility.py +39 -0
  58. daytona_use_computer-0.0.48/use_computer/resources/display.py +39 -0
  59. daytona_use_computer-0.0.48/use_computer/resources/permissions.py +59 -0
  60. daytona_use_computer-0.0.48/use_computer/resources/recording.py +98 -0
  61. daytona_use_computer-0.0.48/use_computer/resources/screenshot.py +63 -0
  62. daytona_use_computer-0.0.48/use_computer/setup_files.py +55 -0
  63. daytona_use_computer-0.0.48/uv.lock +6345 -0
@@ -0,0 +1,6 @@
1
+ # SDK runtime
2
+ USE_COMPUTER_API_KEY=uc_live_...
3
+ USE_COMPUTER_BASE_URL=https://api.use.computer
4
+
5
+ # Local PyPI publish only. Prefer CI Trusted Publishing when possible.
6
+ PYPI_API_TOKEN=pypi-...
@@ -0,0 +1,9 @@
1
+ __pycache__/
2
+ *.pyc
3
+ results/
4
+ dist/
5
+ *.egg-info/
6
+ .env
7
+ .env.*
8
+ !.env.example
9
+ .venv/
@@ -0,0 +1,15 @@
1
+ repos:
2
+ - repo: https://github.com/astral-sh/ruff-pre-commit
3
+ rev: v0.15.12
4
+ hooks:
5
+ - id: ruff-format
6
+ - id: ruff-check
7
+ args: [--fix]
8
+
9
+ - repo: local
10
+ hooks:
11
+ - id: ty-check
12
+ name: ty check
13
+ entry: uv run --group dev ty check .
14
+ language: system
15
+ pass_filenames: false
@@ -0,0 +1,197 @@
1
+ Metadata-Version: 2.5
2
+ Name: daytona-use-computer
3
+ Version: 0.0.48
4
+ Summary: Python SDK for use.computer macOS sandboxes
5
+ Project-URL: Homepage, https://use.computer
6
+ Project-URL: Documentation, https://api.use.computer/docs
7
+ Project-URL: Repository, https://github.com/daytona/use-computer-sdk
8
+ Author: use.computer
9
+ Keywords: automation,computer-use,macos,sandbox,vnc
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Operating System :: OS Independent
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: Programming Language :: Python :: 3.13
18
+ Classifier: Typing :: Typed
19
+ Requires-Python: >=3.10
20
+ Requires-Dist: httpx>=0.27
21
+ Provides-Extra: agents
22
+ Requires-Dist: anthropic>=0.86; extra == 'agents'
23
+ Requires-Dist: google-genai>=1; extra == 'agents'
24
+ Requires-Dist: litellm>=1; extra == 'agents'
25
+ Requires-Dist: openai>=1; extra == 'agents'
26
+ Requires-Dist: pillow>=10; extra == 'agents'
27
+ Requires-Dist: tinker-cookbook>=0.1.0; (python_full_version >= '3.11') and extra == 'agents'
28
+ Requires-Dist: tinker>=0.14.0; (python_full_version >= '3.11') and extra == 'agents'
29
+ Description-Content-Type: text/markdown
30
+
31
+ # use-computer Python SDK (`daytona-use-computer`)
32
+
33
+ use.computer gives you macOS sandboxes: VMs on dedicated Apple M4 Mac minis that you reserve for 24 hours or more, up to 2 VMs at a time per Mac.
34
+
35
+ ```bash
36
+ pip install daytona-use-computer
37
+ export USE_COMPUTER_API_KEY=uc_live_...
38
+ ```
39
+
40
+ Optional agent integrations are installed explicitly:
41
+
42
+ ```bash
43
+ pip install "daytona-use-computer[agents]" # computer-use agents and provider SDKs
44
+ ```
45
+
46
+ Base installs only the SDK client and `httpx`. The `agents` extra installs the agent runtime plus model-provider dependencies (Anthropic, OpenAI, Gemini, LiteLLM).
47
+
48
+ ## Quickstart
49
+
50
+ Flow: sign up → $100 starter credit → reserve a Mac mini (dashboard, or `client.reserve(hours=24)` in the SDK) → `create()` a macOS sandbox → drive it (mouse, keyboard, screenshot, exec, files, recording, UI tree, VNC) → `delete()`. Reservations cost $1.91/hour per Mac; the starter credit pays for them.
51
+
52
+ ```python
53
+ from use_computer import Computer
54
+
55
+ client = Computer()
56
+
57
+ # 1. Reserve one M4 Mac Mini for 24 hours
58
+ reservation = client.reserve(hours=24, mac_model="m4")
59
+
60
+ # 2. Launch a macOS sandbox on the reserved Mac
61
+ with client.create(reservation_id=reservation.id) as mac:
62
+ # 3. Drive the macOS sandbox
63
+ mac.exec("open -a Safari")
64
+ mac.keyboard.type("hello from use.computer")
65
+ mac.mouse.click(500, 500)
66
+ png = mac.screenshot.take_full_screen()
67
+ print("Sandbox:", mac.sandbox_id)
68
+ # Open this sandbox's viewer from the use.computer dashboard.
69
+ ```
70
+
71
+ Never print or share `vnc_url`: it contains your account API key. Open the viewer from the dashboard instead.
72
+
73
+ The optional `mac_model` picks the Mac: `"m4"` (the default; 10 vCPU and 16 GiB RAM) or `"m4-pro"` (12 or more vCPU and 24 GiB RAM). `reservation.mac_model` reports it.
74
+
75
+ Each `create()` can set `cpu`, `memory_gib`, and `disk_gib`. Omitted fields use the Mac's warm VM size, which is 5 vCPU and 8 GiB RAM today and starts right away. Any other size boots a new VM, which can take up to 5 minutes, so `create()` waits up to 10 minutes when you set any of them. A Mac runs at most 2 sandboxes at a time, and their sizes must fit in the Mac. The sandbox reports `cpu`, `memory_gib`, `disk_gib`, and `boot` (`"warm"` or `"cold"`):
76
+
77
+ ```python
78
+ with client.create(reservation_id=reservation.id, cpu=8, memory_gib=12) as mac:
79
+ print(mac.cpu, mac.memory_gib, mac.boot)
80
+ ```
81
+
82
+ If the size does not fit, `create()` raises `SandboxResourcesError`. Its `code` is `invalid_resources`, `resources_exceed_mac`, `sandbox_limit_reached`, or `insufficient_capacity`, and its `message` explains the refusal. `AsyncComputer` accepts the same options. Check `Computer().platforms(reservation_id=...)["macos"]["capacity"]` for the selected reservation's `max` and `used` counts.
83
+
84
+ The older `vm_layout` reservation option still works but is deprecated; the SDK sends it only when you pass it. `"split"` (the server default) allows two sandboxes, and `"whole"` allows one sandbox at a time.
85
+
86
+ When `ephemeral` is omitted, the server default is the reservation lifetime. Explicit `ephemeral=True` opts into destruction after 2 idle minutes; call `sandbox.start_keepalive(interval=30)` during long model-think periods. Explicit `ephemeral=False` disables idle destruction. Context managers still delete their sandbox on exit.
87
+
88
+ This default applies to the source release documented here. Published Python 0.0.46
89
+ still defaults to `ephemeral=True`; pass `ephemeral=False` explicitly on that
90
+ version. The next tagged release includes the omitted-field default above.
91
+
92
+ ## Computer actions
93
+
94
+ Coordinates use screenshot pixels. `mouse.click(x, y, button="middle")` selects the
95
+ middle button; `click_count=3` sends one native triple-click sequence.
96
+ `mouse.down()` / `mouse.up()` hold and release the left button at the current
97
+ cursor, with optional `button`, `x`, and `y`. `keyboard.hold("shift", 0.5)` holds
98
+ keys for seconds. `mouse.drag(..., path=[{"x": 10, "y": 20}, ...])` preserves all
99
+ path points. `mouse.scroll(x, y, scroll_x=40, scroll_y=-80)` sends both pixel
100
+ deltas (positive right/down); the direction/amount form remains available.
101
+ The async methods have the same arguments.
102
+
103
+ Provider mappings are recorded in
104
+ [`action_manifest.json`](use_computer/agents/action_manifest.json).
105
+ Anthropic tool pixels are scaled to sandbox pixels; Gemini uses normalized
106
+ 0..999 coordinates. Control and Command remain distinct. Invalid actions and
107
+ gateway errors are not reported as executed.
108
+
109
+ Agent logs separate action execution from `settle+screenshot`: the latter
110
+ includes a deliberate two-second settle, screenshot requests, and any recovery.
111
+ These are not model inference timings or raw screenshot latency.
112
+
113
+ `exec(command, timeout=120)` uses the gateway's non-login `/bin/zsh -c`;
114
+ `exec_ax` uses `/bin/sh` through the desktop server. The timeout is seconds
115
+ and is sent to the gateway and used as the HTTP timeout. For a 200-second
116
+ command, use `timeout=300`. Results retain separate stdout, stderr, and
117
+ `return_code`; a nonzero process exit is not an HTTP transport failure.
118
+
119
+ ## Application automation permissions
120
+
121
+ macOS asks for consent when a process sends Apple events to another application
122
+ through AppleScript or `osascript`. That consent dialog cannot be answered inside
123
+ the sandbox. The gateway approves every installed application when creating a
124
+ sandbox or restoring a snapshot. After installing another app, call
125
+ `sandbox.permissions.allow_automation()` to approve it before scripting it:
126
+
127
+ ```python
128
+ # Inside an async macOS sandbox session:
129
+ result = await sandbox.exec("brew install --cask firefox")
130
+ if result.return_code != 0:
131
+ raise RuntimeError(result.stderr)
132
+ approval = await sandbox.permissions.allow_automation(
133
+ bundle_ids=["org.mozilla.firefox"]
134
+ )
135
+ print(approval.success, approval.targets, approval.changed)
136
+
137
+ # Alternatively, approve all currently installed applications:
138
+ approval = await sandbox.permissions.allow_automation(installed=True)
139
+ ```
140
+
141
+ The synchronous `MacOSSandbox` exposes the same method without `await`.
142
+ Approvals cover Apple events sent through `osascript`, SSH sessions, `exec`, and
143
+ Terminal.
144
+
145
+ `allow_automation(bundle_ids=None, installed=False)` sends
146
+ `POST /v1/sandboxes/{sandboxID}/permissions/automation`. Supply specific
147
+ `bundle_ids`, `installed=True` for all installed applications, or both in one
148
+ call. Omitted bundle IDs and the default `installed=False` are not sent.
149
+ The 200 response is decoded as the exported `AutomationApproval` dataclass:
150
+ `success: bool`, `targets: int` (applications considered), and `changed: int`
151
+ (approvals added or flipped from denied).
152
+
153
+ The gateway validates bundle IDs against
154
+ `^[A-Za-z0-9][A-Za-z0-9._-]{0,254}$`. Malformed IDs or an empty request receive
155
+ HTTP 400 with `{"error": ...}`. The SDK does not validate them locally; it raises
156
+ the standard `httpx.HTTPStatusError`, whose `response` retains the status and
157
+ error body.
158
+
159
+ ## Runtime Snapshots
160
+
161
+ macOS snapshots let you seed a VM once, snapshot it, then create new sandboxes from that snapshot version. macOS snapshots preserve disk state, so installed apps, accounts, and seeded files are available immediately:
162
+
163
+ ```python
164
+ from use_computer import Computer
165
+
166
+ client = Computer()
167
+
168
+ # Configure the macOS desktop by hand over VNC, then snapshot it.
169
+ with client.create(type="macos") as mac:
170
+ print("Configure sandbox in the dashboard viewer:", mac.sandbox_id)
171
+ input("Press Enter once the desktop is ready to snapshot...")
172
+ snapshot = mac.snapshot("chrome-seeded-macos")
173
+
174
+ # New sandboxes boot from that saved disk state, no setup needed.
175
+ with client.create(type="macos", snapshot=snapshot.version) as seeded:
176
+ print("Seeded sandbox ready:", seeded.sandbox_id)
177
+ ```
178
+
179
+ Use `client.snapshots()` to list saved snapshot versions.
180
+
181
+ ## Examples
182
+
183
+ | File | What it shows |
184
+ | --- | --- |
185
+ | [`examples/_1_hello_macos.py`](../examples/python/_1_hello_macos.py) | reserve → create → exec → keyboard → screenshot |
186
+ | [`examples/_2_recording.py`](../examples/python/_2_recording.py) | start / stop / download a screen recording |
187
+ | [`examples/_3_file_transfer.py`](../examples/python/_3_file_transfer.py) | upload bytes, download a file back |
188
+ | [`examples/_4_keepalive.py`](../examples/python/_4_keepalive.py) | heartbeat for ephemeral sessions idle > 2 min |
189
+ | [`examples/_5_snapshots.py`](../examples/python/_5_snapshots.py) | snapshot seeded macOS state |
190
+
191
+ ## HTTP API
192
+
193
+ Every SDK method wraps `https://api.use.computer/v1/...` with `Authorization: Bearer uc_live_...`. Swagger: [api.use.computer/docs](https://api.use.computer/docs). OpenAPI spec: [api.use.computer/openapi.yaml](https://api.use.computer/openapi.yaml).
194
+
195
+ Gateway URLs, whether passed explicitly or via `USE_COMPUTER_BASE_URL`, must use HTTPS except for loopback HTTP. The SDK does not load `.env` files by default; load one explicitly in your application if needed. Authenticated requests, including keepalive and agent reference screenshots, do not follow redirects.
196
+
197
+ Lifecycle calls (`reserve`, `create`, and `snapshot`, including restores via `create(snapshot=...)`) send a fresh UUID v4 `Idempotency-Key` per call. Idempotent HTTP methods and keyed lifecycle mutations retry transient failures using the same key and body; unkeyed mutations are not retried. A new lifecycle method invocation is a new intent with a new key.
@@ -0,0 +1,167 @@
1
+ # use-computer Python SDK (`daytona-use-computer`)
2
+
3
+ use.computer gives you macOS sandboxes: VMs on dedicated Apple M4 Mac minis that you reserve for 24 hours or more, up to 2 VMs at a time per Mac.
4
+
5
+ ```bash
6
+ pip install daytona-use-computer
7
+ export USE_COMPUTER_API_KEY=uc_live_...
8
+ ```
9
+
10
+ Optional agent integrations are installed explicitly:
11
+
12
+ ```bash
13
+ pip install "daytona-use-computer[agents]" # computer-use agents and provider SDKs
14
+ ```
15
+
16
+ Base installs only the SDK client and `httpx`. The `agents` extra installs the agent runtime plus model-provider dependencies (Anthropic, OpenAI, Gemini, LiteLLM).
17
+
18
+ ## Quickstart
19
+
20
+ Flow: sign up → $100 starter credit → reserve a Mac mini (dashboard, or `client.reserve(hours=24)` in the SDK) → `create()` a macOS sandbox → drive it (mouse, keyboard, screenshot, exec, files, recording, UI tree, VNC) → `delete()`. Reservations cost $1.91/hour per Mac; the starter credit pays for them.
21
+
22
+ ```python
23
+ from use_computer import Computer
24
+
25
+ client = Computer()
26
+
27
+ # 1. Reserve one M4 Mac Mini for 24 hours
28
+ reservation = client.reserve(hours=24, mac_model="m4")
29
+
30
+ # 2. Launch a macOS sandbox on the reserved Mac
31
+ with client.create(reservation_id=reservation.id) as mac:
32
+ # 3. Drive the macOS sandbox
33
+ mac.exec("open -a Safari")
34
+ mac.keyboard.type("hello from use.computer")
35
+ mac.mouse.click(500, 500)
36
+ png = mac.screenshot.take_full_screen()
37
+ print("Sandbox:", mac.sandbox_id)
38
+ # Open this sandbox's viewer from the use.computer dashboard.
39
+ ```
40
+
41
+ Never print or share `vnc_url`: it contains your account API key. Open the viewer from the dashboard instead.
42
+
43
+ The optional `mac_model` picks the Mac: `"m4"` (the default; 10 vCPU and 16 GiB RAM) or `"m4-pro"` (12 or more vCPU and 24 GiB RAM). `reservation.mac_model` reports it.
44
+
45
+ Each `create()` can set `cpu`, `memory_gib`, and `disk_gib`. Omitted fields use the Mac's warm VM size, which is 5 vCPU and 8 GiB RAM today and starts right away. Any other size boots a new VM, which can take up to 5 minutes, so `create()` waits up to 10 minutes when you set any of them. A Mac runs at most 2 sandboxes at a time, and their sizes must fit in the Mac. The sandbox reports `cpu`, `memory_gib`, `disk_gib`, and `boot` (`"warm"` or `"cold"`):
46
+
47
+ ```python
48
+ with client.create(reservation_id=reservation.id, cpu=8, memory_gib=12) as mac:
49
+ print(mac.cpu, mac.memory_gib, mac.boot)
50
+ ```
51
+
52
+ If the size does not fit, `create()` raises `SandboxResourcesError`. Its `code` is `invalid_resources`, `resources_exceed_mac`, `sandbox_limit_reached`, or `insufficient_capacity`, and its `message` explains the refusal. `AsyncComputer` accepts the same options. Check `Computer().platforms(reservation_id=...)["macos"]["capacity"]` for the selected reservation's `max` and `used` counts.
53
+
54
+ The older `vm_layout` reservation option still works but is deprecated; the SDK sends it only when you pass it. `"split"` (the server default) allows two sandboxes, and `"whole"` allows one sandbox at a time.
55
+
56
+ When `ephemeral` is omitted, the server default is the reservation lifetime. Explicit `ephemeral=True` opts into destruction after 2 idle minutes; call `sandbox.start_keepalive(interval=30)` during long model-think periods. Explicit `ephemeral=False` disables idle destruction. Context managers still delete their sandbox on exit.
57
+
58
+ This default applies to the source release documented here. Published Python 0.0.46
59
+ still defaults to `ephemeral=True`; pass `ephemeral=False` explicitly on that
60
+ version. The next tagged release includes the omitted-field default above.
61
+
62
+ ## Computer actions
63
+
64
+ Coordinates use screenshot pixels. `mouse.click(x, y, button="middle")` selects the
65
+ middle button; `click_count=3` sends one native triple-click sequence.
66
+ `mouse.down()` / `mouse.up()` hold and release the left button at the current
67
+ cursor, with optional `button`, `x`, and `y`. `keyboard.hold("shift", 0.5)` holds
68
+ keys for seconds. `mouse.drag(..., path=[{"x": 10, "y": 20}, ...])` preserves all
69
+ path points. `mouse.scroll(x, y, scroll_x=40, scroll_y=-80)` sends both pixel
70
+ deltas (positive right/down); the direction/amount form remains available.
71
+ The async methods have the same arguments.
72
+
73
+ Provider mappings are recorded in
74
+ [`action_manifest.json`](use_computer/agents/action_manifest.json).
75
+ Anthropic tool pixels are scaled to sandbox pixels; Gemini uses normalized
76
+ 0..999 coordinates. Control and Command remain distinct. Invalid actions and
77
+ gateway errors are not reported as executed.
78
+
79
+ Agent logs separate action execution from `settle+screenshot`: the latter
80
+ includes a deliberate two-second settle, screenshot requests, and any recovery.
81
+ These are not model inference timings or raw screenshot latency.
82
+
83
+ `exec(command, timeout=120)` uses the gateway's non-login `/bin/zsh -c`;
84
+ `exec_ax` uses `/bin/sh` through the desktop server. The timeout is seconds
85
+ and is sent to the gateway and used as the HTTP timeout. For a 200-second
86
+ command, use `timeout=300`. Results retain separate stdout, stderr, and
87
+ `return_code`; a nonzero process exit is not an HTTP transport failure.
88
+
89
+ ## Application automation permissions
90
+
91
+ macOS asks for consent when a process sends Apple events to another application
92
+ through AppleScript or `osascript`. That consent dialog cannot be answered inside
93
+ the sandbox. The gateway approves every installed application when creating a
94
+ sandbox or restoring a snapshot. After installing another app, call
95
+ `sandbox.permissions.allow_automation()` to approve it before scripting it:
96
+
97
+ ```python
98
+ # Inside an async macOS sandbox session:
99
+ result = await sandbox.exec("brew install --cask firefox")
100
+ if result.return_code != 0:
101
+ raise RuntimeError(result.stderr)
102
+ approval = await sandbox.permissions.allow_automation(
103
+ bundle_ids=["org.mozilla.firefox"]
104
+ )
105
+ print(approval.success, approval.targets, approval.changed)
106
+
107
+ # Alternatively, approve all currently installed applications:
108
+ approval = await sandbox.permissions.allow_automation(installed=True)
109
+ ```
110
+
111
+ The synchronous `MacOSSandbox` exposes the same method without `await`.
112
+ Approvals cover Apple events sent through `osascript`, SSH sessions, `exec`, and
113
+ Terminal.
114
+
115
+ `allow_automation(bundle_ids=None, installed=False)` sends
116
+ `POST /v1/sandboxes/{sandboxID}/permissions/automation`. Supply specific
117
+ `bundle_ids`, `installed=True` for all installed applications, or both in one
118
+ call. Omitted bundle IDs and the default `installed=False` are not sent.
119
+ The 200 response is decoded as the exported `AutomationApproval` dataclass:
120
+ `success: bool`, `targets: int` (applications considered), and `changed: int`
121
+ (approvals added or flipped from denied).
122
+
123
+ The gateway validates bundle IDs against
124
+ `^[A-Za-z0-9][A-Za-z0-9._-]{0,254}$`. Malformed IDs or an empty request receive
125
+ HTTP 400 with `{"error": ...}`. The SDK does not validate them locally; it raises
126
+ the standard `httpx.HTTPStatusError`, whose `response` retains the status and
127
+ error body.
128
+
129
+ ## Runtime Snapshots
130
+
131
+ macOS snapshots let you seed a VM once, snapshot it, then create new sandboxes from that snapshot version. macOS snapshots preserve disk state, so installed apps, accounts, and seeded files are available immediately:
132
+
133
+ ```python
134
+ from use_computer import Computer
135
+
136
+ client = Computer()
137
+
138
+ # Configure the macOS desktop by hand over VNC, then snapshot it.
139
+ with client.create(type="macos") as mac:
140
+ print("Configure sandbox in the dashboard viewer:", mac.sandbox_id)
141
+ input("Press Enter once the desktop is ready to snapshot...")
142
+ snapshot = mac.snapshot("chrome-seeded-macos")
143
+
144
+ # New sandboxes boot from that saved disk state, no setup needed.
145
+ with client.create(type="macos", snapshot=snapshot.version) as seeded:
146
+ print("Seeded sandbox ready:", seeded.sandbox_id)
147
+ ```
148
+
149
+ Use `client.snapshots()` to list saved snapshot versions.
150
+
151
+ ## Examples
152
+
153
+ | File | What it shows |
154
+ | --- | --- |
155
+ | [`examples/_1_hello_macos.py`](../examples/python/_1_hello_macos.py) | reserve → create → exec → keyboard → screenshot |
156
+ | [`examples/_2_recording.py`](../examples/python/_2_recording.py) | start / stop / download a screen recording |
157
+ | [`examples/_3_file_transfer.py`](../examples/python/_3_file_transfer.py) | upload bytes, download a file back |
158
+ | [`examples/_4_keepalive.py`](../examples/python/_4_keepalive.py) | heartbeat for ephemeral sessions idle > 2 min |
159
+ | [`examples/_5_snapshots.py`](../examples/python/_5_snapshots.py) | snapshot seeded macOS state |
160
+
161
+ ## HTTP API
162
+
163
+ Every SDK method wraps `https://api.use.computer/v1/...` with `Authorization: Bearer uc_live_...`. Swagger: [api.use.computer/docs](https://api.use.computer/docs). OpenAPI spec: [api.use.computer/openapi.yaml](https://api.use.computer/openapi.yaml).
164
+
165
+ Gateway URLs, whether passed explicitly or via `USE_COMPUTER_BASE_URL`, must use HTTPS except for loopback HTTP. The SDK does not load `.env` files by default; load one explicitly in your application if needed. Authenticated requests, including keepalive and agent reference screenshots, do not follow redirects.
166
+
167
+ Lifecycle calls (`reserve`, `create`, and `snapshot`, including restores via `create(snapshot=...)`) send a fresh UUID v4 `Idempotency-Key` per call. Idempotent HTTP methods and keyed lifecycle mutations retry transient failures using the same key and body; unkeyed mutations are not retried. A new lifecycle method invocation is a new intent with a new key.
@@ -0,0 +1,82 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "daytona-use-computer"
7
+ version = "0.0.48"
8
+ description = "Python SDK for use.computer macOS sandboxes"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ authors = [{ name = "use.computer" }]
12
+ dependencies = ["httpx>=0.27"]
13
+ keywords = ["computer-use", "macos", "automation", "sandbox", "vnc"]
14
+ classifiers = [
15
+ "Development Status :: 3 - Alpha",
16
+ "Intended Audience :: Developers",
17
+ "Operating System :: OS Independent",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3.10",
20
+ "Programming Language :: Python :: 3.11",
21
+ "Programming Language :: Python :: 3.12",
22
+ "Programming Language :: Python :: 3.13",
23
+ "Typing :: Typed",
24
+ ]
25
+
26
+ [project.optional-dependencies]
27
+ agents = [
28
+ "Pillow>=10",
29
+ "openai>=1",
30
+ "anthropic>=0.86",
31
+ "litellm>=1",
32
+ "google-genai>=1",
33
+ "tinker>=0.14.0 ; python_full_version >= '3.11'",
34
+ "tinker-cookbook>=0.1.0 ; python_full_version >= '3.11'",
35
+ ]
36
+
37
+
38
+ [dependency-groups]
39
+ dev = [
40
+ "anthropic>=0.86",
41
+ "google-genai>=1",
42
+ "harbor @ git+https://github.com/josancamon19/harbor.git@b0203845795be95f86b149dbc73ab0fd56cf9192 ; python_version >= '3.12'",
43
+ "litellm>=1",
44
+ "openai>=1",
45
+ "pre-commit>=4",
46
+ "pytest>=9",
47
+ "ruff>=0.15",
48
+ "Pillow>=10",
49
+ "PyYAML>=6",
50
+ "tomli>=2",
51
+ "twine>=6",
52
+ "ty>=0.0.34",
53
+ ]
54
+
55
+ [project.urls]
56
+ Homepage = "https://use.computer"
57
+ Documentation = "https://api.use.computer/docs"
58
+ Repository = "https://github.com/daytona/use-computer-sdk"
59
+
60
+ [tool.hatch.build.targets.wheel]
61
+ packages = ["use_computer"]
62
+
63
+ [tool.hatch.build]
64
+ exclude = [
65
+ "/.github",
66
+ "/.pytest_cache",
67
+ "/.ruff_cache",
68
+ "/.venv",
69
+ "/dist",
70
+ "/results",
71
+ "/tests",
72
+ ]
73
+
74
+ [tool.pytest.ini_options]
75
+ testpaths = ["tests"]
76
+
77
+ [tool.ruff]
78
+ target-version = "py310"
79
+ line-length = 100
80
+
81
+ [tool.ruff.lint]
82
+ select = ["E", "F", "I", "UP"]
@@ -0,0 +1,193 @@
1
+ """Smoke-test the built wheel in clean environments."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import subprocess
7
+ import sys
8
+ import tempfile
9
+ import zipfile
10
+ from pathlib import Path
11
+
12
+ BASE_SMOKE = r"""
13
+ import importlib.util
14
+ import use_computer
15
+
16
+ def has(name):
17
+ try:
18
+ return importlib.util.find_spec(name) is not None
19
+ except ModuleNotFoundError:
20
+ return False
21
+
22
+ assert "site-packages" in (use_computer.__file__ or ""), use_computer.__file__
23
+ for name in ["PIL", "openai", "anthropic", "google.genai", "litellm"]:
24
+ assert not has(name), name
25
+ """
26
+
27
+ AGENTS_SMOKE = r"""
28
+ import asyncio
29
+ import importlib
30
+ import importlib.util
31
+ import tempfile
32
+ from pathlib import Path
33
+
34
+ assert importlib.util.find_spec("PIL") is not None
35
+ for name in ["openai", "anthropic", "google.genai", "litellm"]:
36
+ assert importlib.util.find_spec(name) is not None, name
37
+ for path in ["use_computer.agents", "use_computer.agents.debug"]:
38
+ importlib.import_module(path)
39
+
40
+ from use_computer.agents import (
41
+ AnthropicComputerAgent,
42
+ GeminiComputerAgent,
43
+ GenericComputerAgent,
44
+
45
+ OpenAIComputerAgent,
46
+ )
47
+ from use_computer.agents.debug import DebugComputerAgent
48
+ from use_computer.core.models import ExecResult
49
+
50
+ PNG_BYTES = (
51
+ b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01"
52
+ b"\x08\x02\x00\x00\x00\x90wS\xde\x00\x00\x00\x0cIDAT\x08\xd7c\xf8\xff"
53
+ b"\xff?\x00\x05\xfe\x02\xfeA\xe2`\\\x00\x00\x00\x00IEND\xaeB`\x82"
54
+ )
55
+
56
+ class Recording:
57
+ async def start(self, name="trial"):
58
+ raise RuntimeError("recording disabled")
59
+
60
+ class Screenshot:
61
+ async def take_full_screen(self):
62
+ return PNG_BYTES
63
+
64
+ class Display:
65
+ async def get_windows(self):
66
+ return [{"role": "window", "title": "Fake"}]
67
+
68
+ class Mouse:
69
+ def __init__(self):
70
+ self.clicks = []
71
+
72
+ async def click(self, x, y, button="left", double=False):
73
+ self.clicks.append((x, y, button, double))
74
+
75
+ class Sandbox:
76
+ def __init__(self):
77
+ self.sandbox_id = "fake"
78
+ self.screenshot = Screenshot()
79
+ self.recording = Recording()
80
+ self.display = Display()
81
+ self.mouse = Mouse()
82
+ self.commands = []
83
+
84
+ async def exec_ssh(self, command, timeout=120):
85
+ self.commands.append(command)
86
+ return ExecResult(return_code=0, stdout="ok", stderr="")
87
+
88
+ with tempfile.TemporaryDirectory() as tmp:
89
+ root = Path(tmp)
90
+ sandbox = Sandbox()
91
+ result = asyncio.run(
92
+ DebugComputerAgent(max_steps=1).run(
93
+ "smoke",
94
+ sandbox=sandbox,
95
+ logs_dir=root / "agent",
96
+ task_dir=root / "task",
97
+ )
98
+ )
99
+ assert sandbox.mouse.clicks == [(960, 540, "left", False)]
100
+ assert sandbox.commands == ["echo hello world"]
101
+ assert result.final_message == "smoke"
102
+
103
+ for agent_cls, model_name in [
104
+ (AnthropicComputerAgent, "anthropic/claude-sonnet-4-6"),
105
+ (OpenAIComputerAgent, "openai/gpt-5.5"),
106
+ (GeminiComputerAgent, "gemini/gemini-2.5-computer-use-preview-10-2025"),
107
+ (GenericComputerAgent, "openai/gpt-4o"),
108
+
109
+ ]:
110
+ assert agent_cls(model_name=model_name, max_steps=1).max_steps == 1
111
+ """
112
+
113
+
114
+ def main() -> None:
115
+ parser = argparse.ArgumentParser()
116
+ parser.add_argument("wheel", type=Path)
117
+ args = parser.parse_args()
118
+
119
+ wheel = args.wheel.resolve()
120
+ if not wheel.name.endswith(".whl"):
121
+ raise SystemExit(f"expected a wheel path, got {wheel}")
122
+
123
+ _check_wheel_contents(wheel)
124
+ _check_metadata(wheel)
125
+ _smoke_install(wheel, None, BASE_SMOKE)
126
+ _smoke_install(wheel, "agents", AGENTS_SMOKE)
127
+
128
+
129
+ def _check_wheel_contents(wheel: Path) -> None:
130
+ with zipfile.ZipFile(wheel) as archive:
131
+ names = set(archive.namelist())
132
+
133
+ required = {
134
+ "use_computer/py.typed",
135
+ "use_computer/agents/prompts/anthropic.txt",
136
+ "use_computer/agents/prompts/gemini.txt",
137
+ "use_computer/agents/prompts/openai.txt",
138
+ "use_computer/agents/prompts/pyautogui.txt",
139
+ }
140
+ missing = sorted(required - names)
141
+ if missing:
142
+ raise SystemExit(f"wheel is missing files: {missing}")
143
+
144
+ forbidden = [name for name in names if "__pycache__" in name or name.startswith("tests/")]
145
+ if forbidden:
146
+ raise SystemExit(f"wheel contains generated/test files: {sorted(forbidden)[:10]}")
147
+
148
+
149
+ def _check_metadata(wheel: Path) -> None:
150
+ with zipfile.ZipFile(wheel) as archive:
151
+ metadata_name = next(
152
+ name for name in archive.namelist() if name.endswith(".dist-info/METADATA")
153
+ )
154
+ metadata = archive.read(metadata_name).decode()
155
+
156
+ required = [
157
+ "Provides-Extra: agents",
158
+ "Requires-Dist: pillow>=10; extra == 'agents'",
159
+ "Requires-Dist: openai>=1; extra == 'agents'",
160
+ "Requires-Dist: anthropic>=0.86; extra == 'agents'",
161
+ "Requires-Dist: litellm>=1; extra == 'agents'",
162
+ "Requires-Dist: google-genai>=1; extra == 'agents'",
163
+ ]
164
+ missing = [line for line in required if line not in metadata]
165
+ if missing:
166
+ raise SystemExit(f"wheel metadata is missing: {missing}")
167
+
168
+ forbidden = [
169
+ f"Provides-Extra: agents-{name}"
170
+ for name in ("openai", "anthropic", "litellm", "gemini", "all")
171
+ ]
172
+ present = [line for line in forbidden if line in metadata]
173
+ if present:
174
+ raise SystemExit(f"wheel metadata still has provider-specific extras: {present}")
175
+
176
+
177
+ def _smoke_install(wheel: Path, extra: str | None, code: str) -> None:
178
+ with tempfile.TemporaryDirectory(prefix="use-computer-wheel-") as tmp:
179
+ tmp_path = Path(tmp)
180
+ venv = tmp_path / "venv"
181
+ _run(["uv", "venv", "--python", sys.executable, str(venv)], cwd=tmp_path)
182
+ python = venv / ("Scripts/python.exe" if sys.platform == "win32" else "bin/python")
183
+ requirement = f"{wheel}[{extra}]" if extra else str(wheel)
184
+ _run(["uv", "pip", "install", "--python", str(python), requirement], cwd=tmp_path)
185
+ _run([str(python), "-c", code], cwd=tmp_path)
186
+
187
+
188
+ def _run(cmd: list[str], *, cwd: Path) -> None:
189
+ subprocess.run(cmd, cwd=cwd, check=True)
190
+
191
+
192
+ if __name__ == "__main__":
193
+ main()