use-computer 0.0.18__tar.gz → 0.0.20__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.
- {use_computer-0.0.18 → use_computer-0.0.20}/PKG-INFO +16 -1
- {use_computer-0.0.18 → use_computer-0.0.20}/README.md +15 -0
- use_computer-0.0.20/examples/_7_hello_windows.py +11 -0
- {use_computer-0.0.18 → use_computer-0.0.20}/pyproject.toml +1 -1
- {use_computer-0.0.18 → use_computer-0.0.20}/use_computer/__init__.py +4 -0
- {use_computer-0.0.18 → use_computer-0.0.20}/use_computer/client.py +49 -3
- {use_computer-0.0.18 → use_computer-0.0.20}/use_computer/errors.py +7 -1
- {use_computer-0.0.18 → use_computer-0.0.20}/use_computer/recording.py +6 -2
- {use_computer-0.0.18 → use_computer-0.0.20}/use_computer/sandbox.py +123 -0
- use_computer-0.0.20/use_computer/windows/__init__.py +3 -0
- use_computer-0.0.20/use_computer/windows/shell.py +57 -0
- {use_computer-0.0.18 → use_computer-0.0.20}/.env.example +0 -0
- {use_computer-0.0.18 → use_computer-0.0.20}/.gitignore +0 -0
- {use_computer-0.0.18 → use_computer-0.0.20}/.pre-commit-config.yaml +0 -0
- {use_computer-0.0.18 → use_computer-0.0.20}/examples/_1_hello_macos.py +0 -0
- {use_computer-0.0.18 → use_computer-0.0.20}/examples/_2_hello_ios.py +0 -0
- {use_computer-0.0.18 → use_computer-0.0.20}/examples/_3_recording.py +0 -0
- {use_computer-0.0.18 → use_computer-0.0.20}/examples/_4_file_transfer.py +0 -0
- {use_computer-0.0.18 → use_computer-0.0.20}/examples/_5_keepalive.py +0 -0
- {use_computer-0.0.18 → use_computer-0.0.20}/examples/_6_hello_tvos.py +0 -0
- {use_computer-0.0.18 → use_computer-0.0.20}/use_computer/accessibility.py +0 -0
- {use_computer-0.0.18 → use_computer-0.0.20}/use_computer/ax_transpile.py +0 -0
- {use_computer-0.0.18 → use_computer-0.0.20}/use_computer/display.py +0 -0
- {use_computer-0.0.18 → use_computer-0.0.20}/use_computer/ios/__init__.py +0 -0
- {use_computer-0.0.18 → use_computer-0.0.20}/use_computer/ios/apps.py +0 -0
- {use_computer-0.0.18 → use_computer-0.0.20}/use_computer/ios/environment.py +0 -0
- {use_computer-0.0.18 → use_computer-0.0.20}/use_computer/ios/input.py +0 -0
- {use_computer-0.0.18 → use_computer-0.0.20}/use_computer/macos/__init__.py +0 -0
- {use_computer-0.0.18 → use_computer-0.0.20}/use_computer/macos/keyboard.py +0 -0
- {use_computer-0.0.18 → use_computer-0.0.20}/use_computer/macos/mouse.py +0 -0
- {use_computer-0.0.18 → use_computer-0.0.20}/use_computer/models.py +0 -0
- {use_computer-0.0.18 → use_computer-0.0.20}/use_computer/parsers.py +0 -0
- {use_computer-0.0.18 → use_computer-0.0.20}/use_computer/py.typed +0 -0
- {use_computer-0.0.18 → use_computer-0.0.20}/use_computer/retry.py +0 -0
- {use_computer-0.0.18 → use_computer-0.0.20}/use_computer/screenshot.py +0 -0
- {use_computer-0.0.18 → use_computer-0.0.20}/use_computer/simulators.py +0 -0
- {use_computer-0.0.18 → use_computer-0.0.20}/use_computer/tasks/__init__.py +0 -0
- {use_computer-0.0.18 → use_computer-0.0.20}/use_computer/tasks/templates/pre_command.sh +0 -0
- {use_computer-0.0.18 → use_computer-0.0.20}/use_computer/tasks/templates/task.toml +0 -0
- {use_computer-0.0.18 → use_computer-0.0.20}/use_computer/tasks/templates/test_ios.sh +0 -0
- {use_computer-0.0.18 → use_computer-0.0.20}/use_computer/tasks/templates/test_ios_nograder.sh +0 -0
- {use_computer-0.0.18 → use_computer-0.0.20}/use_computer/tasks/templates/test_macos.sh +0 -0
- {use_computer-0.0.18 → use_computer-0.0.20}/use_computer/tasks/templates/test_macos_check.sh +0 -0
- {use_computer-0.0.18 → use_computer-0.0.20}/use_computer/tasks/templates/test_macos_nograder.sh +0 -0
- {use_computer-0.0.18 → use_computer-0.0.20}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: use-computer
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.20
|
|
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
|
|
@@ -45,6 +45,21 @@ with Computer().create(type=SandboxType.IOS) as ios:
|
|
|
45
45
|
ios.input.long_press(120, 300, duration=1.0)
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
+
### Windows (Beta)
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
with Computer().create(type="windows") as win:
|
|
52
|
+
print(win.run("$env:COMPUTERNAME").stdout) # PowerShell exec (no SSH)
|
|
53
|
+
win.keyboard.type("hello")
|
|
54
|
+
win.screenshot.take_full_screen()
|
|
55
|
+
win.ui_tree() # native Windows UIAutomation tree
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Windows sandboxes are **Beta** (admin-only). Same mouse/keyboard/screenshot/
|
|
59
|
+
recording/file surface as macOS; exec runs in-guest via PowerShell/cmd
|
|
60
|
+
(`win.run` / `win.shell`) instead of SSH. `AsyncWindowsSandbox` mirrors it.
|
|
61
|
+
See [docs.use.computer/docs/windows](https://docs.use.computer/docs/windows).
|
|
62
|
+
|
|
48
63
|
Full DSL reference (macOS + simulator): [docs.use.computer/docs/sdk](https://docs.use.computer/docs/sdk)
|
|
49
64
|
|
|
50
65
|
Simulator sandboxes use `type=SandboxType.IOS` for the SDK route, but
|
|
@@ -23,6 +23,21 @@ with Computer().create(type=SandboxType.IOS) as ios:
|
|
|
23
23
|
ios.input.long_press(120, 300, duration=1.0)
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
+
### Windows (Beta)
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
with Computer().create(type="windows") as win:
|
|
30
|
+
print(win.run("$env:COMPUTERNAME").stdout) # PowerShell exec (no SSH)
|
|
31
|
+
win.keyboard.type("hello")
|
|
32
|
+
win.screenshot.take_full_screen()
|
|
33
|
+
win.ui_tree() # native Windows UIAutomation tree
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Windows sandboxes are **Beta** (admin-only). Same mouse/keyboard/screenshot/
|
|
37
|
+
recording/file surface as macOS; exec runs in-guest via PowerShell/cmd
|
|
38
|
+
(`win.run` / `win.shell`) instead of SSH. `AsyncWindowsSandbox` mirrors it.
|
|
39
|
+
See [docs.use.computer/docs/windows](https://docs.use.computer/docs/windows).
|
|
40
|
+
|
|
26
41
|
Full DSL reference (macOS + simulator): [docs.use.computer/docs/sdk](https://docs.use.computer/docs/sdk)
|
|
27
42
|
|
|
28
43
|
Simulator sandboxes use `type=SandboxType.IOS` for the SDK route, but
|
|
@@ -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)")
|
|
@@ -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__(
|
|
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
|
-
|
|
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
|
-
|
|
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,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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{use_computer-0.0.18 → use_computer-0.0.20}/use_computer/tasks/templates/test_ios_nograder.sh
RENAMED
|
File without changes
|
|
File without changes
|
{use_computer-0.0.18 → use_computer-0.0.20}/use_computer/tasks/templates/test_macos_check.sh
RENAMED
|
File without changes
|
{use_computer-0.0.18 → use_computer-0.0.20}/use_computer/tasks/templates/test_macos_nograder.sh
RENAMED
|
File without changes
|
|
File without changes
|