use-computer 0.0.18__tar.gz → 0.0.19__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 (45) hide show
  1. {use_computer-0.0.18 → use_computer-0.0.19}/PKG-INFO +1 -1
  2. use_computer-0.0.19/examples/_7_hello_windows.py +11 -0
  3. {use_computer-0.0.18 → use_computer-0.0.19}/pyproject.toml +1 -1
  4. {use_computer-0.0.18 → use_computer-0.0.19}/use_computer/__init__.py +4 -0
  5. {use_computer-0.0.18 → use_computer-0.0.19}/use_computer/client.py +49 -3
  6. {use_computer-0.0.18 → use_computer-0.0.19}/use_computer/errors.py +7 -1
  7. {use_computer-0.0.18 → use_computer-0.0.19}/use_computer/recording.py +6 -2
  8. {use_computer-0.0.18 → use_computer-0.0.19}/use_computer/sandbox.py +123 -0
  9. use_computer-0.0.19/use_computer/windows/__init__.py +3 -0
  10. use_computer-0.0.19/use_computer/windows/shell.py +57 -0
  11. {use_computer-0.0.18 → use_computer-0.0.19}/.env.example +0 -0
  12. {use_computer-0.0.18 → use_computer-0.0.19}/.gitignore +0 -0
  13. {use_computer-0.0.18 → use_computer-0.0.19}/.pre-commit-config.yaml +0 -0
  14. {use_computer-0.0.18 → use_computer-0.0.19}/README.md +0 -0
  15. {use_computer-0.0.18 → use_computer-0.0.19}/examples/_1_hello_macos.py +0 -0
  16. {use_computer-0.0.18 → use_computer-0.0.19}/examples/_2_hello_ios.py +0 -0
  17. {use_computer-0.0.18 → use_computer-0.0.19}/examples/_3_recording.py +0 -0
  18. {use_computer-0.0.18 → use_computer-0.0.19}/examples/_4_file_transfer.py +0 -0
  19. {use_computer-0.0.18 → use_computer-0.0.19}/examples/_5_keepalive.py +0 -0
  20. {use_computer-0.0.18 → use_computer-0.0.19}/examples/_6_hello_tvos.py +0 -0
  21. {use_computer-0.0.18 → use_computer-0.0.19}/use_computer/accessibility.py +0 -0
  22. {use_computer-0.0.18 → use_computer-0.0.19}/use_computer/ax_transpile.py +0 -0
  23. {use_computer-0.0.18 → use_computer-0.0.19}/use_computer/display.py +0 -0
  24. {use_computer-0.0.18 → use_computer-0.0.19}/use_computer/ios/__init__.py +0 -0
  25. {use_computer-0.0.18 → use_computer-0.0.19}/use_computer/ios/apps.py +0 -0
  26. {use_computer-0.0.18 → use_computer-0.0.19}/use_computer/ios/environment.py +0 -0
  27. {use_computer-0.0.18 → use_computer-0.0.19}/use_computer/ios/input.py +0 -0
  28. {use_computer-0.0.18 → use_computer-0.0.19}/use_computer/macos/__init__.py +0 -0
  29. {use_computer-0.0.18 → use_computer-0.0.19}/use_computer/macos/keyboard.py +0 -0
  30. {use_computer-0.0.18 → use_computer-0.0.19}/use_computer/macos/mouse.py +0 -0
  31. {use_computer-0.0.18 → use_computer-0.0.19}/use_computer/models.py +0 -0
  32. {use_computer-0.0.18 → use_computer-0.0.19}/use_computer/parsers.py +0 -0
  33. {use_computer-0.0.18 → use_computer-0.0.19}/use_computer/py.typed +0 -0
  34. {use_computer-0.0.18 → use_computer-0.0.19}/use_computer/retry.py +0 -0
  35. {use_computer-0.0.18 → use_computer-0.0.19}/use_computer/screenshot.py +0 -0
  36. {use_computer-0.0.18 → use_computer-0.0.19}/use_computer/simulators.py +0 -0
  37. {use_computer-0.0.18 → use_computer-0.0.19}/use_computer/tasks/__init__.py +0 -0
  38. {use_computer-0.0.18 → use_computer-0.0.19}/use_computer/tasks/templates/pre_command.sh +0 -0
  39. {use_computer-0.0.18 → use_computer-0.0.19}/use_computer/tasks/templates/task.toml +0 -0
  40. {use_computer-0.0.18 → use_computer-0.0.19}/use_computer/tasks/templates/test_ios.sh +0 -0
  41. {use_computer-0.0.18 → use_computer-0.0.19}/use_computer/tasks/templates/test_ios_nograder.sh +0 -0
  42. {use_computer-0.0.18 → use_computer-0.0.19}/use_computer/tasks/templates/test_macos.sh +0 -0
  43. {use_computer-0.0.18 → use_computer-0.0.19}/use_computer/tasks/templates/test_macos_check.sh +0 -0
  44. {use_computer-0.0.18 → use_computer-0.0.19}/use_computer/tasks/templates/test_macos_nograder.sh +0 -0
  45. {use_computer-0.0.18 → use_computer-0.0.19}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: use-computer
3
- Version: 0.0.18
3
+ Version: 0.0.19
4
4
  Summary: Python SDK for the use.computer macOS and iOS Computer Use API
5
5
  Project-URL: Homepage, https://use.computer
6
6
  Project-URL: Documentation, https://api.use.computer/docs
@@ -0,0 +1,11 @@
1
+ from use_computer import Computer
2
+
3
+ # Beta: Windows sandboxes. No SSH — exec runs in-guest via PowerShell/cmd.
4
+ with Computer().create(type="windows") as win:
5
+ win.run("Start-Process notepad") # PowerShell by default
6
+ win.keyboard.type("hello from use.computer")
7
+ print(win.run("$env:COMPUTERNAME", shell="powershell").stdout)
8
+
9
+ png = win.screenshot.take_full_screen()
10
+ open("hello_windows.png", "wb").write(png)
11
+ print(f"saved hello_windows.png ({len(png)} bytes)")
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "use-computer"
7
- version = "0.0.18"
7
+ version = "0.0.19"
8
8
  description = "Python SDK for the use.computer macOS and iOS Computer Use API"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -15,10 +15,12 @@ from use_computer.sandbox import (
15
15
  AsyncIOSSandbox,
16
16
  AsyncMacOSSandbox,
17
17
  AsyncSandbox,
18
+ AsyncWindowsSandbox,
18
19
  IOSSandbox,
19
20
  MacOSSandbox,
20
21
  Sandbox,
21
22
  SandboxType,
23
+ WindowsSandbox,
22
24
  )
23
25
  from use_computer.simulators import (
24
26
  SimulatorChoice,
@@ -40,6 +42,7 @@ __all__ = [
40
42
  "AsyncMacOSSandbox",
41
43
  "AsyncComputer",
42
44
  "AsyncSandbox",
45
+ "AsyncWindowsSandbox",
43
46
  "Button",
44
47
  "Computer",
45
48
  "CursorPosition",
@@ -60,6 +63,7 @@ __all__ = [
60
63
  "TaskSummary",
61
64
  "TasksClient",
62
65
  "UseComputerError",
66
+ "WindowsSandbox",
63
67
  "family_for_device",
64
68
  "is_usable_device_type",
65
69
  "parse_pyautogui",
@@ -14,17 +14,19 @@ from use_computer.sandbox import (
14
14
  AsyncIOSSandbox,
15
15
  AsyncMacOSSandbox,
16
16
  AsyncSandbox,
17
+ AsyncWindowsSandbox,
17
18
  IOSSandbox,
18
19
  MacOSSandbox,
19
20
  Sandbox,
20
21
  SandboxType,
22
+ WindowsSandbox,
21
23
  )
22
24
  from use_computer.simulators import SimulatorFamily, select_simulator
23
25
  from use_computer.tasks import TasksClient
24
26
 
25
27
  DEFAULT_BASE_URL = "https://api.use.computer"
26
28
 
27
- SandboxTypeInput = SandboxType | Literal["macos", "ios"]
29
+ SandboxTypeInput = SandboxType | Literal["macos", "ios", "windows"]
28
30
 
29
31
 
30
32
  def _resolve_api_key(api_key: str | None) -> str | None:
@@ -128,6 +130,14 @@ class Computer:
128
130
  runtime: str = ...,
129
131
  ) -> IOSSandbox: ...
130
132
 
133
+ @overload
134
+ def create(
135
+ self,
136
+ *,
137
+ type: Literal["windows", SandboxType.WINDOWS],
138
+ host: str = ...,
139
+ ) -> WindowsSandbox: ...
140
+
131
141
  def create(
132
142
  self,
133
143
  *,
@@ -136,7 +146,7 @@ class Computer:
136
146
  family: SimulatorFamily | str | None = None,
137
147
  device_type: str = "",
138
148
  runtime: str = "",
139
- ) -> MacOSSandbox | IOSSandbox:
149
+ ) -> MacOSSandbox | IOSSandbox | WindowsSandbox:
140
150
  """Create a new sandbox.
141
151
 
142
152
  Args:
@@ -167,6 +177,13 @@ class Computer:
167
177
 
168
178
  if sandbox_type == SandboxType.IOS:
169
179
  return IOSSandbox(sandbox_id=sid, http=self._http)
180
+ if sandbox_type == SandboxType.WINDOWS:
181
+ return WindowsSandbox(
182
+ sandbox_id=sid,
183
+ http=self._http,
184
+ vnc_url=f"{self._base_url}{data.get('vnc_url', '')}",
185
+ host=data.get("host", ""),
186
+ )
170
187
  return MacOSSandbox(
171
188
  sandbox_id=sid,
172
189
  http=self._http,
@@ -184,6 +201,13 @@ class Computer:
184
201
  sb_type = SandboxType(data.get("type", "macos"))
185
202
  if sb_type == SandboxType.IOS:
186
203
  return IOSSandbox(sandbox_id=data["sandbox_id"], http=self._http)
204
+ if sb_type == SandboxType.WINDOWS:
205
+ return WindowsSandbox(
206
+ sandbox_id=data["sandbox_id"],
207
+ http=self._http,
208
+ vnc_url=f"{self._base_url}{data.get('vnc_url', '')}",
209
+ host=data.get("host", ""),
210
+ )
187
211
  return MacOSSandbox(
188
212
  sandbox_id=data["sandbox_id"],
189
213
  http=self._http,
@@ -292,6 +316,14 @@ class AsyncComputer:
292
316
  runtime: str = ...,
293
317
  ) -> AsyncIOSSandbox: ...
294
318
 
319
+ @overload
320
+ async def create(
321
+ self,
322
+ *,
323
+ type: Literal["windows", SandboxType.WINDOWS],
324
+ host: str = ...,
325
+ ) -> AsyncWindowsSandbox: ...
326
+
295
327
  async def create(
296
328
  self,
297
329
  *,
@@ -300,7 +332,7 @@ class AsyncComputer:
300
332
  family: SimulatorFamily | str | None = None,
301
333
  device_type: str = "",
302
334
  runtime: str = "",
303
- ) -> AsyncMacOSSandbox | AsyncIOSSandbox:
335
+ ) -> AsyncMacOSSandbox | AsyncIOSSandbox | AsyncWindowsSandbox:
304
336
  """Create a new sandbox.
305
337
 
306
338
  Args:
@@ -331,6 +363,13 @@ class AsyncComputer:
331
363
 
332
364
  if sandbox_type == SandboxType.IOS:
333
365
  return AsyncIOSSandbox(sandbox_id=sid, http=self._http)
366
+ if sandbox_type == SandboxType.WINDOWS:
367
+ return AsyncWindowsSandbox(
368
+ sandbox_id=sid,
369
+ http=self._http,
370
+ vnc_url=f"{self._base_url}{data.get('vnc_url', '')}",
371
+ host=data.get("host", ""),
372
+ )
334
373
  return AsyncMacOSSandbox(
335
374
  sandbox_id=sid,
336
375
  http=self._http,
@@ -348,6 +387,13 @@ class AsyncComputer:
348
387
  sb_type = SandboxType(data.get("type", "macos"))
349
388
  if sb_type == SandboxType.IOS:
350
389
  return AsyncIOSSandbox(sandbox_id=data["sandbox_id"], http=self._http)
390
+ if sb_type == SandboxType.WINDOWS:
391
+ return AsyncWindowsSandbox(
392
+ sandbox_id=data["sandbox_id"],
393
+ http=self._http,
394
+ vnc_url=f"{self._base_url}{data.get('vnc_url', '')}",
395
+ host=data.get("host", ""),
396
+ )
351
397
  return AsyncMacOSSandbox(
352
398
  sandbox_id=data["sandbox_id"],
353
399
  http=self._http,
@@ -18,7 +18,13 @@ class PlatformNotSupportedError(UseComputerError):
18
18
  "hint": "use POST /tap"}
19
19
  """
20
20
 
21
- def __init__(self, action: str, sandbox_type: str, hint: str, response: httpx.Response):
21
+ def __init__(
22
+ self,
23
+ action: str,
24
+ sandbox_type: str,
25
+ hint: str,
26
+ response: httpx.Response | None = None,
27
+ ):
22
28
  self.action = action
23
29
  self.sandbox_type = sandbox_type
24
30
  self.hint = hint
@@ -26,7 +26,9 @@ class Recording:
26
26
  def list_all(self) -> list[RecordingInfo]:
27
27
  resp = self._http.get(f"{self._prefix}/recordings")
28
28
  resp.raise_for_status()
29
- return [RecordingInfo.from_dict(r) for r in resp.json()]
29
+ data = resp.json()
30
+ items = data.get("recordings", []) if isinstance(data, dict) else data
31
+ return [RecordingInfo.from_dict(r) for r in items]
30
32
 
31
33
  def get(self, recording_id: str) -> RecordingInfo:
32
34
  resp = self._http.get(f"{self._prefix}/recordings/{recording_id}")
@@ -67,7 +69,9 @@ class AsyncRecording:
67
69
  async def list_all(self) -> list[RecordingInfo]:
68
70
  resp = await self._http.get(f"{self._prefix}/recordings")
69
71
  resp.raise_for_status()
70
- return [RecordingInfo.from_dict(r) for r in resp.json()]
72
+ data = resp.json()
73
+ items = data.get("recordings", []) if isinstance(data, dict) else data
74
+ return [RecordingInfo.from_dict(r) for r in items]
71
75
 
72
76
  async def get(self, recording_id: str) -> RecordingInfo:
73
77
  resp = await self._http.get(f"{self._prefix}/recordings/{recording_id}")
@@ -11,6 +11,7 @@ import httpx
11
11
 
12
12
  from use_computer.accessibility import Accessibility, AsyncAccessibility
13
13
  from use_computer.display import AsyncDisplay, Display
14
+ from use_computer.errors import PlatformNotSupportedError
14
15
  from use_computer.ios.apps import Apps, AsyncApps
15
16
  from use_computer.ios.environment import AsyncEnvironment, Environment
16
17
  from use_computer.ios.input import AsyncInput, Input
@@ -19,11 +20,13 @@ from use_computer.macos.mouse import AsyncMouse, Mouse
19
20
  from use_computer.models import ActResult, ExecResult
20
21
  from use_computer.recording import AsyncRecording, Recording
21
22
  from use_computer.screenshot import AsyncScreenshot, Screenshot
23
+ from use_computer.windows.shell import AsyncShell, Shell, ShellKind
22
24
 
23
25
 
24
26
  class SandboxType(str, Enum):
25
27
  MACOS = "macos"
26
28
  IOS = "ios"
29
+ WINDOWS = "windows"
27
30
 
28
31
 
29
32
  # ---------------------------------------------------------------------------
@@ -252,6 +255,75 @@ class IOSSandbox(Sandbox):
252
255
  self.environment = Environment(http, self._prefix)
253
256
 
254
257
 
258
+ class _NoAxeAccessibility:
259
+ """Mixin: Windows has no macOS axe-schema tree — `.accessibility` raises, use `.ui_tree()`."""
260
+
261
+ @property
262
+ def accessibility(self):
263
+ raise PlatformNotSupportedError(
264
+ action="accessibility (macOS axe schema)",
265
+ sandbox_type="windows",
266
+ hint="use .ui_tree() for the native Windows UIAutomation tree",
267
+ )
268
+
269
+ @accessibility.setter
270
+ def accessibility(self, _value):
271
+ pass # discard the base's axe helper — Windows exposes .ui_tree() instead
272
+
273
+
274
+ class WindowsSandbox(_NoAxeAccessibility, Sandbox):
275
+ """Windows VM sandbox (Beta) — mouse, keyboard, PowerShell/cmd exec, VNC.
276
+
277
+ Beta. Differs from macOS: no SSH (exec runs through the in-guest agent via
278
+ `shell`), and `ui_tree()` returns the native Windows UIAutomation tree. The
279
+ macOS axe-schema `.accessibility` raises PlatformNotSupportedError here.
280
+ """
281
+
282
+ def __init__(
283
+ self,
284
+ sandbox_id: str,
285
+ http: httpx.Client,
286
+ *,
287
+ vnc_url: str = "",
288
+ host: str = "",
289
+ ):
290
+ super().__init__(sandbox_id, SandboxType.WINDOWS, http, vnc_url=vnc_url)
291
+ self.host = host
292
+ # Mouse/keyboard hit the same gateway command path as macOS.
293
+ self.mouse = Mouse(http, self._prefix)
294
+ self.keyboard = Keyboard(http, self._prefix)
295
+ self.shell = Shell(http, self._prefix)
296
+
297
+ def act(
298
+ self,
299
+ action: dict,
300
+ screenshot_after: bool = True,
301
+ screenshot_delay_ms: int = 100,
302
+ ) -> ActResult:
303
+ resp = self._http.post(
304
+ f"{self._prefix}/act",
305
+ json={
306
+ "action": action,
307
+ "screenshot_after": screenshot_after,
308
+ "screenshot_delay_ms": screenshot_delay_ms,
309
+ },
310
+ )
311
+ resp.raise_for_status()
312
+ if screenshot_after and resp.headers.get("content-type", "").startswith("image/"):
313
+ return ActResult(screenshot=resp.content)
314
+ return ActResult(data=resp.json())
315
+
316
+ def run(self, command: str, shell: ShellKind = "powershell", timeout: int = 300) -> ExecResult:
317
+ """Shorthand for self.shell.run()."""
318
+ return self.shell.run(command, shell, timeout)
319
+
320
+ def ui_tree(self, max_depth: int = 12) -> dict:
321
+ """Native Windows UIAutomation tree (raw, not normalized to the axe schema)."""
322
+ resp = self._http.get(f"{self._prefix}/display/windows", params={"max_depth": max_depth})
323
+ resp.raise_for_status()
324
+ return resp.json()
325
+
326
+
255
327
  # ---------------------------------------------------------------------------
256
328
  # Async
257
329
  # ---------------------------------------------------------------------------
@@ -420,3 +492,54 @@ class AsyncIOSSandbox(AsyncSandbox):
420
492
  self.input = AsyncInput(http, self._prefix)
421
493
  self.apps = AsyncApps(http, self._prefix)
422
494
  self.environment = AsyncEnvironment(http, self._prefix)
495
+
496
+
497
+ class AsyncWindowsSandbox(_NoAxeAccessibility, AsyncSandbox):
498
+ """Async Windows VM sandbox — see WindowsSandbox."""
499
+
500
+ def __init__(
501
+ self,
502
+ sandbox_id: str,
503
+ http: httpx.AsyncClient,
504
+ *,
505
+ vnc_url: str = "",
506
+ host: str = "",
507
+ ):
508
+ super().__init__(sandbox_id, SandboxType.WINDOWS, http, vnc_url=vnc_url)
509
+ self.host = host
510
+ self.mouse = AsyncMouse(http, self._prefix)
511
+ self.keyboard = AsyncKeyboard(http, self._prefix)
512
+ self.shell = AsyncShell(http, self._prefix)
513
+
514
+ async def act(
515
+ self,
516
+ action: dict,
517
+ screenshot_after: bool = True,
518
+ screenshot_delay_ms: int = 100,
519
+ ) -> ActResult:
520
+ resp = await self._http.post(
521
+ f"{self._prefix}/act",
522
+ json={
523
+ "action": action,
524
+ "screenshot_after": screenshot_after,
525
+ "screenshot_delay_ms": screenshot_delay_ms,
526
+ },
527
+ )
528
+ resp.raise_for_status()
529
+ if screenshot_after and resp.headers.get("content-type", "").startswith("image/"):
530
+ return ActResult(screenshot=resp.content)
531
+ return ActResult(data=resp.json())
532
+
533
+ async def run(
534
+ self, command: str, shell: ShellKind = "powershell", timeout: int = 300
535
+ ) -> ExecResult:
536
+ """Shorthand for self.shell.run()."""
537
+ return await self.shell.run(command, shell, timeout)
538
+
539
+ async def ui_tree(self, max_depth: int = 12) -> dict:
540
+ """Native Windows UIAutomation tree (raw, not normalized to the axe schema)."""
541
+ resp = await self._http.get(
542
+ f"{self._prefix}/display/windows", params={"max_depth": max_depth}
543
+ )
544
+ resp.raise_for_status()
545
+ return resp.json()
@@ -0,0 +1,3 @@
1
+ from use_computer.windows.shell import AsyncShell, Shell
2
+
3
+ __all__ = ["AsyncShell", "Shell"]
@@ -0,0 +1,57 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Literal
4
+
5
+ import httpx
6
+
7
+ from use_computer.models import ExecResult
8
+
9
+ ShellKind = Literal["powershell", "cmd"]
10
+
11
+
12
+ class Shell:
13
+ """Run commands inside a Windows sandbox via the in-guest agent (no SSH)."""
14
+
15
+ def __init__(self, http: httpx.Client, prefix: str):
16
+ self._http = http
17
+ self._prefix = prefix
18
+
19
+ def run(self, command: str, shell: ShellKind = "powershell", timeout: int = 300) -> ExecResult:
20
+ resp = self._http.post(
21
+ f"{self._prefix}/exec",
22
+ json={"command": command, "shell": shell},
23
+ timeout=timeout,
24
+ )
25
+ resp.raise_for_status()
26
+ return ExecResult.from_dict(resp.json())
27
+
28
+ def powershell(self, command: str, timeout: int = 300) -> ExecResult:
29
+ return self.run(command, "powershell", timeout)
30
+
31
+ def cmd(self, command: str, timeout: int = 300) -> ExecResult:
32
+ return self.run(command, "cmd", timeout)
33
+
34
+
35
+ class AsyncShell:
36
+ """Async variant of Shell."""
37
+
38
+ def __init__(self, http: httpx.AsyncClient, prefix: str):
39
+ self._http = http
40
+ self._prefix = prefix
41
+
42
+ async def run(
43
+ self, command: str, shell: ShellKind = "powershell", timeout: int = 300
44
+ ) -> ExecResult:
45
+ resp = await self._http.post(
46
+ f"{self._prefix}/exec",
47
+ json={"command": command, "shell": shell},
48
+ timeout=timeout,
49
+ )
50
+ resp.raise_for_status()
51
+ return ExecResult.from_dict(resp.json())
52
+
53
+ async def powershell(self, command: str, timeout: int = 300) -> ExecResult:
54
+ return await self.run(command, "powershell", timeout)
55
+
56
+ async def cmd(self, command: str, timeout: int = 300) -> ExecResult:
57
+ return await self.run(command, "cmd", timeout)
File without changes
File without changes
File without changes