use-computer 0.0.24__tar.gz → 0.0.26__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.24 → use_computer-0.0.26}/PKG-INFO +7 -5
- {use_computer-0.0.24 → use_computer-0.0.26}/README.md +4 -2
- {use_computer-0.0.24 → use_computer-0.0.26}/pyproject.toml +3 -3
- {use_computer-0.0.24 → use_computer-0.0.26}/use_computer/__init__.py +4 -0
- {use_computer-0.0.24 → use_computer-0.0.26}/use_computer/client.py +39 -7
- {use_computer-0.0.24 → use_computer-0.0.26}/use_computer/models.py +7 -1
- {use_computer-0.0.24 → use_computer-0.0.26}/use_computer/sandbox.py +139 -4
- {use_computer-0.0.24 → use_computer-0.0.26}/use_computer/windows/shell.py +1 -1
- {use_computer-0.0.24 → use_computer-0.0.26}/.env.example +0 -0
- {use_computer-0.0.24 → use_computer-0.0.26}/.gitignore +0 -0
- {use_computer-0.0.24 → use_computer-0.0.26}/.pre-commit-config.yaml +0 -0
- {use_computer-0.0.24 → use_computer-0.0.26}/use_computer/accessibility.py +0 -0
- {use_computer-0.0.24 → use_computer-0.0.26}/use_computer/ax_transpile.py +0 -0
- {use_computer-0.0.24 → use_computer-0.0.26}/use_computer/display.py +0 -0
- {use_computer-0.0.24 → use_computer-0.0.26}/use_computer/errors.py +0 -0
- {use_computer-0.0.24 → use_computer-0.0.26}/use_computer/ios/__init__.py +0 -0
- {use_computer-0.0.24 → use_computer-0.0.26}/use_computer/ios/apps.py +0 -0
- {use_computer-0.0.24 → use_computer-0.0.26}/use_computer/ios/environment.py +0 -0
- {use_computer-0.0.24 → use_computer-0.0.26}/use_computer/ios/input.py +0 -0
- {use_computer-0.0.24 → use_computer-0.0.26}/use_computer/macos/__init__.py +0 -0
- {use_computer-0.0.24 → use_computer-0.0.26}/use_computer/macos/keyboard.py +0 -0
- {use_computer-0.0.24 → use_computer-0.0.26}/use_computer/macos/mouse.py +0 -0
- {use_computer-0.0.24 → use_computer-0.0.26}/use_computer/parsers.py +0 -0
- {use_computer-0.0.24 → use_computer-0.0.26}/use_computer/py.typed +0 -0
- {use_computer-0.0.24 → use_computer-0.0.26}/use_computer/recording.py +0 -0
- {use_computer-0.0.24 → use_computer-0.0.26}/use_computer/retry.py +0 -0
- {use_computer-0.0.24 → use_computer-0.0.26}/use_computer/screenshot.py +0 -0
- {use_computer-0.0.24 → use_computer-0.0.26}/use_computer/simulators.py +0 -0
- {use_computer-0.0.24 → use_computer-0.0.26}/use_computer/tasks/__init__.py +0 -0
- {use_computer-0.0.24 → use_computer-0.0.26}/use_computer/tasks/templates/pre_command.sh +0 -0
- {use_computer-0.0.24 → use_computer-0.0.26}/use_computer/tasks/templates/task.toml +0 -0
- {use_computer-0.0.24 → use_computer-0.0.26}/use_computer/tasks/templates/test_ios.sh +0 -0
- {use_computer-0.0.24 → use_computer-0.0.26}/use_computer/tasks/templates/test_ios_nograder.sh +0 -0
- {use_computer-0.0.24 → use_computer-0.0.26}/use_computer/tasks/templates/test_macos.sh +0 -0
- {use_computer-0.0.24 → use_computer-0.0.26}/use_computer/tasks/templates/test_macos_check.sh +0 -0
- {use_computer-0.0.24 → use_computer-0.0.26}/use_computer/tasks/templates/test_macos_nograder.sh +0 -0
- {use_computer-0.0.24 → use_computer-0.0.26}/use_computer/windows/__init__.py +0 -0
- {use_computer-0.0.24 → use_computer-0.0.26}/uv.lock +0 -0
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: use-computer
|
|
3
|
-
Version: 0.0.
|
|
4
|
-
Summary: Python SDK for
|
|
3
|
+
Version: 0.0.26
|
|
4
|
+
Summary: Python SDK for use.computer sandboxes across macOS, iOS simulators, Windows, and Ubuntu
|
|
5
5
|
Project-URL: Homepage, https://use.computer
|
|
6
6
|
Project-URL: Documentation, https://api.use.computer/docs
|
|
7
7
|
Project-URL: Repository, https://github.com/josancamon19/use-computer-python
|
|
8
8
|
Author: use.computer
|
|
9
|
-
Keywords: automation,computer-use,ios,macos,sandbox
|
|
9
|
+
Keywords: automation,computer-use,ios,macos,sandbox,ubuntu,vnc,windows
|
|
10
10
|
Classifier: Development Status :: 3 - Alpha
|
|
11
11
|
Classifier: Intended Audience :: Developers
|
|
12
12
|
Classifier: Operating System :: OS Independent
|
|
@@ -22,7 +22,7 @@ Description-Content-Type: text/markdown
|
|
|
22
22
|
|
|
23
23
|
# use-computer Python SDK
|
|
24
24
|
|
|
25
|
-
Python client for [use.computer](https://use.computer) — rent dedicated
|
|
25
|
+
Python client for [use.computer](https://use.computer) — rent dedicated VMs across macOS, iOS/visionOS/tvOS simulators, Windows, and Ubuntu built for computer-use agents.
|
|
26
26
|
|
|
27
27
|
```bash
|
|
28
28
|
pip install use-computer
|
|
@@ -73,7 +73,7 @@ Ubuntu sandboxes are **Beta** (admin-only). They use the same KVM/noVNC backend
|
|
|
73
73
|
as Windows; pass `version="ubuntu-24.04"` to select a specific image. Exec
|
|
74
74
|
defaults to bash and the macOS accessibility tree is not available yet.
|
|
75
75
|
|
|
76
|
-
Full DSL reference
|
|
76
|
+
Full DSL reference: [docs.use.computer/docs/sdk](https://docs.use.computer/docs/sdk)
|
|
77
77
|
|
|
78
78
|
Simulator sandboxes use `type=SandboxType.IOS` for the SDK route, but
|
|
79
79
|
`device_type` and `runtime` can target any installed compatible CoreSimulator
|
|
@@ -105,6 +105,8 @@ visionOS yet. Use it for read-only flows for now.
|
|
|
105
105
|
| [`examples/_4_file_transfer.py`](examples/_4_file_transfer.py) | upload bytes, download a file back |
|
|
106
106
|
| [`examples/_5_keepalive.py`](examples/_5_keepalive.py) | heartbeat for sessions idle > 2 min |
|
|
107
107
|
| [`examples/_6_hello_tvos.py`](examples/_6_hello_tvos.py) | tvOS: pick TV family + drive the Apple Remote |
|
|
108
|
+
| [`examples/_7_hello_windows.py`](examples/_7_hello_windows.py) | create Windows → run PowerShell → screenshot |
|
|
109
|
+
| [`examples/_8_hello_ubuntu.py`](examples/_8_hello_ubuntu.py) | create Ubuntu → run bash → screenshot |
|
|
108
110
|
|
|
109
111
|
For agent loops and evals: [use-computer-cookbook](https://github.com/josancamon19/use-computer-cookbook).
|
|
110
112
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# use-computer Python SDK
|
|
2
2
|
|
|
3
|
-
Python client for [use.computer](https://use.computer) — rent dedicated
|
|
3
|
+
Python client for [use.computer](https://use.computer) — rent dedicated VMs across macOS, iOS/visionOS/tvOS simulators, Windows, and Ubuntu built for computer-use agents.
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
6
|
pip install use-computer
|
|
@@ -51,7 +51,7 @@ Ubuntu sandboxes are **Beta** (admin-only). They use the same KVM/noVNC backend
|
|
|
51
51
|
as Windows; pass `version="ubuntu-24.04"` to select a specific image. Exec
|
|
52
52
|
defaults to bash and the macOS accessibility tree is not available yet.
|
|
53
53
|
|
|
54
|
-
Full DSL reference
|
|
54
|
+
Full DSL reference: [docs.use.computer/docs/sdk](https://docs.use.computer/docs/sdk)
|
|
55
55
|
|
|
56
56
|
Simulator sandboxes use `type=SandboxType.IOS` for the SDK route, but
|
|
57
57
|
`device_type` and `runtime` can target any installed compatible CoreSimulator
|
|
@@ -83,6 +83,8 @@ visionOS yet. Use it for read-only flows for now.
|
|
|
83
83
|
| [`examples/_4_file_transfer.py`](examples/_4_file_transfer.py) | upload bytes, download a file back |
|
|
84
84
|
| [`examples/_5_keepalive.py`](examples/_5_keepalive.py) | heartbeat for sessions idle > 2 min |
|
|
85
85
|
| [`examples/_6_hello_tvos.py`](examples/_6_hello_tvos.py) | tvOS: pick TV family + drive the Apple Remote |
|
|
86
|
+
| [`examples/_7_hello_windows.py`](examples/_7_hello_windows.py) | create Windows → run PowerShell → screenshot |
|
|
87
|
+
| [`examples/_8_hello_ubuntu.py`](examples/_8_hello_ubuntu.py) | create Ubuntu → run bash → screenshot |
|
|
86
88
|
|
|
87
89
|
For agent loops and evals: [use-computer-cookbook](https://github.com/josancamon19/use-computer-cookbook).
|
|
88
90
|
|
|
@@ -4,13 +4,13 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "use-computer"
|
|
7
|
-
version = "0.0.
|
|
8
|
-
description = "Python SDK for
|
|
7
|
+
version = "0.0.26"
|
|
8
|
+
description = "Python SDK for use.computer sandboxes across macOS, iOS simulators, Windows, and Ubuntu"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
11
11
|
authors = [{ name = "use.computer" }]
|
|
12
12
|
dependencies = ["httpx>=0.27"]
|
|
13
|
-
keywords = ["computer-use", "macos", "ios", "automation", "sandbox"]
|
|
13
|
+
keywords = ["computer-use", "macos", "ios", "windows", "ubuntu", "automation", "sandbox", "vnc"]
|
|
14
14
|
classifiers = [
|
|
15
15
|
"Development Status :: 3 - Alpha",
|
|
16
16
|
"Intended Audience :: Developers",
|
|
@@ -17,10 +17,12 @@ from use_computer.sandbox import (
|
|
|
17
17
|
AsyncSandbox,
|
|
18
18
|
AsyncUbuntuSandbox,
|
|
19
19
|
AsyncWindowsSandbox,
|
|
20
|
+
HostRedirect,
|
|
20
21
|
IOSSandbox,
|
|
21
22
|
MacOSSandbox,
|
|
22
23
|
Sandbox,
|
|
23
24
|
SandboxType,
|
|
25
|
+
Snapshot,
|
|
24
26
|
UbuntuSandbox,
|
|
25
27
|
WindowsSandbox,
|
|
26
28
|
)
|
|
@@ -51,6 +53,7 @@ __all__ = [
|
|
|
51
53
|
"CursorPosition",
|
|
52
54
|
"DisplayInfo",
|
|
53
55
|
"ExecResult",
|
|
56
|
+
"HostRedirect",
|
|
54
57
|
"IOSSandbox",
|
|
55
58
|
"Key",
|
|
56
59
|
"MacOSSandbox",
|
|
@@ -60,6 +63,7 @@ __all__ = [
|
|
|
60
63
|
"RunStatus",
|
|
61
64
|
"Sandbox",
|
|
62
65
|
"SandboxType",
|
|
66
|
+
"Snapshot",
|
|
63
67
|
"SimulatorChoice",
|
|
64
68
|
"SimulatorFamily",
|
|
65
69
|
"Task",
|
|
@@ -91,7 +91,7 @@ class RunStatus:
|
|
|
91
91
|
|
|
92
92
|
|
|
93
93
|
class Computer:
|
|
94
|
-
"""use.computer client. Creates and manages macOS and
|
|
94
|
+
"""use.computer client. Creates and manages macOS, iOS, Windows, and Ubuntu sandboxes."""
|
|
95
95
|
|
|
96
96
|
def __init__(self, api_key: str | None = None, base_url: str | None = None):
|
|
97
97
|
self._base_url = _resolve_base_url(base_url)
|
|
@@ -114,6 +114,13 @@ class Computer:
|
|
|
114
114
|
resp.raise_for_status()
|
|
115
115
|
return resp.json()
|
|
116
116
|
|
|
117
|
+
def snapshots(self, platform: str = "") -> list[dict[str, Any]]:
|
|
118
|
+
"""List Windur desktop snapshots visible to this API key."""
|
|
119
|
+
params = {"platform": platform} if platform else None
|
|
120
|
+
resp = self._http.get("/v1/snapshots", params=params)
|
|
121
|
+
resp.raise_for_status()
|
|
122
|
+
return resp.json().get("snapshots", [])
|
|
123
|
+
|
|
117
124
|
@overload
|
|
118
125
|
def create(
|
|
119
126
|
self,
|
|
@@ -138,6 +145,7 @@ class Computer:
|
|
|
138
145
|
*,
|
|
139
146
|
type: Literal["windows", SandboxType.WINDOWS],
|
|
140
147
|
version: str = ...,
|
|
148
|
+
snapshot: str = ...,
|
|
141
149
|
host: str = ...,
|
|
142
150
|
) -> WindowsSandbox: ...
|
|
143
151
|
|
|
@@ -147,6 +155,7 @@ class Computer:
|
|
|
147
155
|
*,
|
|
148
156
|
type: Literal["ubuntu", SandboxType.UBUNTU],
|
|
149
157
|
version: str = ...,
|
|
158
|
+
snapshot: str = ...,
|
|
150
159
|
host: str = ...,
|
|
151
160
|
) -> UbuntuSandbox: ...
|
|
152
161
|
|
|
@@ -159,24 +168,31 @@ class Computer:
|
|
|
159
168
|
device_type: str = "",
|
|
160
169
|
runtime: str = "",
|
|
161
170
|
version: str = "",
|
|
171
|
+
snapshot: str = "",
|
|
162
172
|
) -> MacOSSandbox | IOSSandbox | WindowsSandbox | UbuntuSandbox:
|
|
163
173
|
"""Create a new sandbox.
|
|
164
174
|
|
|
165
175
|
Args:
|
|
166
|
-
type: SandboxType.MACOS (default),
|
|
176
|
+
type: SandboxType.MACOS (default), IOS, WINDOWS, UBUNTU, or the raw API string.
|
|
167
177
|
host: Pin sandbox to a specific host machine (e.g. "mm001").
|
|
168
178
|
family: Simulator family enum/alias, for example SimulatorFamily.TV.
|
|
169
179
|
device_type: Simulator device type identifier, for exact pinning.
|
|
170
180
|
runtime: Simulator runtime identifier, for exact pinning.
|
|
171
181
|
version: KVM desktop image version, for example "windows-11",
|
|
172
182
|
"windows-10", or "ubuntu-24.04".
|
|
183
|
+
snapshot: Windur snapshot version returned by sandbox.snapshot().
|
|
173
184
|
"""
|
|
174
185
|
sandbox_type = _normalize_sandbox_type(type)
|
|
175
186
|
body: dict = {"type": sandbox_type.value}
|
|
176
187
|
if host:
|
|
177
188
|
body["host"] = host
|
|
178
|
-
if sandbox_type in (SandboxType.WINDOWS, SandboxType.UBUNTU)
|
|
179
|
-
|
|
189
|
+
if sandbox_type in (SandboxType.WINDOWS, SandboxType.UBUNTU):
|
|
190
|
+
if version and snapshot:
|
|
191
|
+
raise ValueError("version and snapshot are mutually exclusive")
|
|
192
|
+
if snapshot:
|
|
193
|
+
body["snapshot"] = snapshot
|
|
194
|
+
elif version:
|
|
195
|
+
body["version"] = version
|
|
180
196
|
if sandbox_type == SandboxType.IOS:
|
|
181
197
|
if family and (not device_type or not runtime):
|
|
182
198
|
choice = select_simulator(self.platforms(), family)
|
|
@@ -333,6 +349,13 @@ class AsyncComputer:
|
|
|
333
349
|
resp.raise_for_status()
|
|
334
350
|
return resp.json()
|
|
335
351
|
|
|
352
|
+
async def snapshots(self, platform: str = "") -> list[dict[str, Any]]:
|
|
353
|
+
"""List Windur desktop snapshots visible to this API key."""
|
|
354
|
+
params = {"platform": platform} if platform else None
|
|
355
|
+
resp = await self._http.get("/v1/snapshots", params=params)
|
|
356
|
+
resp.raise_for_status()
|
|
357
|
+
return resp.json().get("snapshots", [])
|
|
358
|
+
|
|
336
359
|
@overload
|
|
337
360
|
async def create(
|
|
338
361
|
self,
|
|
@@ -357,6 +380,7 @@ class AsyncComputer:
|
|
|
357
380
|
*,
|
|
358
381
|
type: Literal["windows", SandboxType.WINDOWS],
|
|
359
382
|
version: str = ...,
|
|
383
|
+
snapshot: str = ...,
|
|
360
384
|
host: str = ...,
|
|
361
385
|
) -> AsyncWindowsSandbox: ...
|
|
362
386
|
|
|
@@ -366,6 +390,7 @@ class AsyncComputer:
|
|
|
366
390
|
*,
|
|
367
391
|
type: Literal["ubuntu", SandboxType.UBUNTU],
|
|
368
392
|
version: str = ...,
|
|
393
|
+
snapshot: str = ...,
|
|
369
394
|
host: str = ...,
|
|
370
395
|
) -> AsyncUbuntuSandbox: ...
|
|
371
396
|
|
|
@@ -378,24 +403,31 @@ class AsyncComputer:
|
|
|
378
403
|
device_type: str = "",
|
|
379
404
|
runtime: str = "",
|
|
380
405
|
version: str = "",
|
|
406
|
+
snapshot: str = "",
|
|
381
407
|
) -> AsyncMacOSSandbox | AsyncIOSSandbox | AsyncWindowsSandbox | AsyncUbuntuSandbox:
|
|
382
408
|
"""Create a new sandbox.
|
|
383
409
|
|
|
384
410
|
Args:
|
|
385
|
-
type: SandboxType.MACOS (default),
|
|
411
|
+
type: SandboxType.MACOS (default), IOS, WINDOWS, UBUNTU, or the raw API string.
|
|
386
412
|
host: Pin sandbox to a specific host machine (e.g. "mm001").
|
|
387
413
|
family: Simulator family enum/alias, for example SimulatorFamily.TV.
|
|
388
414
|
device_type: Simulator device type identifier, for exact pinning.
|
|
389
415
|
runtime: Simulator runtime identifier, for exact pinning.
|
|
390
416
|
version: KVM desktop image version, for example "windows-11",
|
|
391
417
|
"windows-10", or "ubuntu-24.04".
|
|
418
|
+
snapshot: Windur snapshot version returned by sandbox.snapshot().
|
|
392
419
|
"""
|
|
393
420
|
sandbox_type = _normalize_sandbox_type(type)
|
|
394
421
|
body: dict = {"type": sandbox_type.value}
|
|
395
422
|
if host:
|
|
396
423
|
body["host"] = host
|
|
397
|
-
if sandbox_type in (SandboxType.WINDOWS, SandboxType.UBUNTU)
|
|
398
|
-
|
|
424
|
+
if sandbox_type in (SandboxType.WINDOWS, SandboxType.UBUNTU):
|
|
425
|
+
if version and snapshot:
|
|
426
|
+
raise ValueError("version and snapshot are mutually exclusive")
|
|
427
|
+
if snapshot:
|
|
428
|
+
body["snapshot"] = snapshot
|
|
429
|
+
elif version:
|
|
430
|
+
body["version"] = version
|
|
399
431
|
if sandbox_type == SandboxType.IOS:
|
|
400
432
|
if family and (not device_type or not runtime):
|
|
401
433
|
choice = select_simulator(await self.platforms(), family)
|
|
@@ -88,12 +88,18 @@ class RecordingInfo:
|
|
|
88
88
|
class ExecResult:
|
|
89
89
|
return_code: int
|
|
90
90
|
stdout: str
|
|
91
|
+
stderr: str = ""
|
|
92
|
+
|
|
93
|
+
@property
|
|
94
|
+
def exit_code(self) -> int:
|
|
95
|
+
return self.return_code
|
|
91
96
|
|
|
92
97
|
@classmethod
|
|
93
98
|
def from_dict(cls, d: dict) -> ExecResult:
|
|
94
99
|
return cls(
|
|
95
|
-
return_code=d.get("return_code", 0),
|
|
100
|
+
return_code=d.get("return_code", d.get("exit_code", d.get("returncode", 0))),
|
|
96
101
|
stdout=d.get("stdout", ""),
|
|
102
|
+
stderr=d.get("stderr", ""),
|
|
97
103
|
)
|
|
98
104
|
|
|
99
105
|
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import base64
|
|
3
4
|
import io
|
|
4
5
|
import tarfile
|
|
5
6
|
import threading
|
|
6
7
|
import urllib.request
|
|
8
|
+
from dataclasses import dataclass
|
|
7
9
|
from enum import Enum
|
|
8
10
|
from pathlib import Path
|
|
9
11
|
|
|
@@ -30,6 +32,15 @@ class SandboxType(str, Enum):
|
|
|
30
32
|
UBUNTU = "ubuntu"
|
|
31
33
|
|
|
32
34
|
|
|
35
|
+
@dataclass
|
|
36
|
+
class Snapshot:
|
|
37
|
+
"""Windur-backed desktop snapshot."""
|
|
38
|
+
|
|
39
|
+
name: str
|
|
40
|
+
version: str
|
|
41
|
+
platform: str
|
|
42
|
+
|
|
43
|
+
|
|
33
44
|
# ---------------------------------------------------------------------------
|
|
34
45
|
# Keepalive helper — shared by sync and async sandboxes
|
|
35
46
|
# ---------------------------------------------------------------------------
|
|
@@ -134,6 +145,26 @@ class Sandbox:
|
|
|
134
145
|
resp.raise_for_status()
|
|
135
146
|
local_path.write_bytes(resp.content)
|
|
136
147
|
|
|
148
|
+
def snapshot(self, name: str, *, version: str = "", timeout: int = 900) -> Snapshot:
|
|
149
|
+
"""Capture a Windur desktop snapshot and return its image version."""
|
|
150
|
+
if self.type not in (SandboxType.WINDOWS, SandboxType.UBUNTU):
|
|
151
|
+
raise PlatformNotSupportedError(
|
|
152
|
+
action="snapshot",
|
|
153
|
+
sandbox_type=self.type.value,
|
|
154
|
+
hint="snapshots are supported for Windows and Ubuntu sandboxes",
|
|
155
|
+
)
|
|
156
|
+
body = {"name": name}
|
|
157
|
+
if version:
|
|
158
|
+
body["version"] = version
|
|
159
|
+
resp = self._http.post(f"{self._prefix}/snapshots", json=body, timeout=timeout)
|
|
160
|
+
resp.raise_for_status()
|
|
161
|
+
data = resp.json()
|
|
162
|
+
return Snapshot(
|
|
163
|
+
name=data.get("name", name),
|
|
164
|
+
version=data.get("version", version),
|
|
165
|
+
platform=data.get("platform", self.type.value),
|
|
166
|
+
)
|
|
167
|
+
|
|
137
168
|
def start_keepalive(self, interval: float = 30.0) -> None:
|
|
138
169
|
"""Start a background thread that pings the gateway every `interval` seconds."""
|
|
139
170
|
self._keepalive_stop, self._keepalive_thread = _start_keepalive_thread(
|
|
@@ -256,6 +287,90 @@ class IOSSandbox(Sandbox):
|
|
|
256
287
|
self.environment = Environment(http, self._prefix)
|
|
257
288
|
|
|
258
289
|
|
|
290
|
+
@dataclass
|
|
291
|
+
class HostRedirect:
|
|
292
|
+
"""Point a domain at the loopback host (the /etc/hosts redirect) so a browser
|
|
293
|
+
hitting it lands on a locally-running app. If ``port`` is set, :80 and :443
|
|
294
|
+
are also forwarded to 127.0.0.1:port, so e.g. ``https://mail.google.com``
|
|
295
|
+
reaches a local server on that port."""
|
|
296
|
+
|
|
297
|
+
host: str
|
|
298
|
+
ip: str = "127.0.0.1"
|
|
299
|
+
port: int | None = None
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def _build_setup_spec(
|
|
303
|
+
hosts: list[HostRedirect] | None,
|
|
304
|
+
files: dict[str, bytes | str] | None,
|
|
305
|
+
script: str | None,
|
|
306
|
+
open_urls: list[str] | None,
|
|
307
|
+
open_apps: list[str] | None,
|
|
308
|
+
) -> dict:
|
|
309
|
+
spec: dict = {}
|
|
310
|
+
if hosts:
|
|
311
|
+
spec["hosts"] = [
|
|
312
|
+
{"host": h.host, "ip": h.ip, **({"port": h.port} if h.port else {})} for h in hosts
|
|
313
|
+
]
|
|
314
|
+
if files:
|
|
315
|
+
spec["files"] = [
|
|
316
|
+
{
|
|
317
|
+
"path": path,
|
|
318
|
+
"content_b64": base64.b64encode(
|
|
319
|
+
data if isinstance(data, bytes) else data.encode()
|
|
320
|
+
).decode(),
|
|
321
|
+
}
|
|
322
|
+
for path, data in files.items()
|
|
323
|
+
]
|
|
324
|
+
if script:
|
|
325
|
+
spec["script"] = script
|
|
326
|
+
if open_urls:
|
|
327
|
+
spec["open_urls"] = list(open_urls)
|
|
328
|
+
if open_apps:
|
|
329
|
+
spec["open_apps"] = list(open_apps)
|
|
330
|
+
return spec
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
class _Seeding:
|
|
334
|
+
"""Typed seeding for KVM desktops (windows/ubuntu), applied in order:
|
|
335
|
+
host redirects → files → script → open_urls → open_apps."""
|
|
336
|
+
|
|
337
|
+
_http: httpx.Client
|
|
338
|
+
_prefix: str
|
|
339
|
+
|
|
340
|
+
def setup(
|
|
341
|
+
self,
|
|
342
|
+
*,
|
|
343
|
+
hosts: list[HostRedirect] | None = None,
|
|
344
|
+
files: dict[str, bytes | str] | None = None,
|
|
345
|
+
script: str | None = None,
|
|
346
|
+
open_urls: list[str] | None = None,
|
|
347
|
+
open_apps: list[str] | None = None,
|
|
348
|
+
) -> None:
|
|
349
|
+
spec = _build_setup_spec(hosts, files, script, open_urls, open_apps)
|
|
350
|
+
resp = self._http.post(f"{self._prefix}/setup", json=spec, timeout=1200)
|
|
351
|
+
resp.raise_for_status()
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
class _AsyncSeeding:
|
|
355
|
+
"""Async counterpart of _Seeding."""
|
|
356
|
+
|
|
357
|
+
_http: httpx.AsyncClient
|
|
358
|
+
_prefix: str
|
|
359
|
+
|
|
360
|
+
async def setup(
|
|
361
|
+
self,
|
|
362
|
+
*,
|
|
363
|
+
hosts: list[HostRedirect] | None = None,
|
|
364
|
+
files: dict[str, bytes | str] | None = None,
|
|
365
|
+
script: str | None = None,
|
|
366
|
+
open_urls: list[str] | None = None,
|
|
367
|
+
open_apps: list[str] | None = None,
|
|
368
|
+
) -> None:
|
|
369
|
+
spec = _build_setup_spec(hosts, files, script, open_urls, open_apps)
|
|
370
|
+
resp = await self._http.post(f"{self._prefix}/setup", json=spec, timeout=1200)
|
|
371
|
+
resp.raise_for_status()
|
|
372
|
+
|
|
373
|
+
|
|
259
374
|
class _NoAxeAccessibility:
|
|
260
375
|
"""Mixin for desktop guests without the macOS axe-schema tree."""
|
|
261
376
|
|
|
@@ -275,7 +390,7 @@ class _NoAxeAccessibility:
|
|
|
275
390
|
pass # discard the base's axe helper — Windows exposes .ui_tree() instead
|
|
276
391
|
|
|
277
392
|
|
|
278
|
-
class WindowsSandbox(_NoAxeAccessibility, Sandbox):
|
|
393
|
+
class WindowsSandbox(_Seeding, _NoAxeAccessibility, Sandbox):
|
|
279
394
|
"""Windows VM sandbox (Beta) — mouse, keyboard, PowerShell/cmd exec, VNC.
|
|
280
395
|
|
|
281
396
|
Beta. Differs from macOS: no SSH (exec runs through the in-guest agent via
|
|
@@ -333,7 +448,7 @@ class WindowsSandbox(_NoAxeAccessibility, Sandbox):
|
|
|
333
448
|
return resp.json()
|
|
334
449
|
|
|
335
450
|
|
|
336
|
-
class UbuntuSandbox(_NoAxeAccessibility, Sandbox):
|
|
451
|
+
class UbuntuSandbox(_Seeding, _NoAxeAccessibility, Sandbox):
|
|
337
452
|
"""Ubuntu desktop VM sandbox — mouse, keyboard, bash exec, VNC."""
|
|
338
453
|
|
|
339
454
|
_accessibility_sandbox_type = "ubuntu"
|
|
@@ -431,6 +546,26 @@ class AsyncSandbox:
|
|
|
431
546
|
resp.raise_for_status()
|
|
432
547
|
local_path.write_bytes(resp.content)
|
|
433
548
|
|
|
549
|
+
async def snapshot(self, name: str, *, version: str = "", timeout: int = 900) -> Snapshot:
|
|
550
|
+
"""Capture a Windur desktop snapshot and return its image version."""
|
|
551
|
+
if self.type not in (SandboxType.WINDOWS, SandboxType.UBUNTU):
|
|
552
|
+
raise PlatformNotSupportedError(
|
|
553
|
+
action="snapshot",
|
|
554
|
+
sandbox_type=self.type.value,
|
|
555
|
+
hint="snapshots are supported for Windows and Ubuntu sandboxes",
|
|
556
|
+
)
|
|
557
|
+
body = {"name": name}
|
|
558
|
+
if version:
|
|
559
|
+
body["version"] = version
|
|
560
|
+
resp = await self._http.post(f"{self._prefix}/snapshots", json=body, timeout=timeout)
|
|
561
|
+
resp.raise_for_status()
|
|
562
|
+
data = resp.json()
|
|
563
|
+
return Snapshot(
|
|
564
|
+
name=data.get("name", name),
|
|
565
|
+
version=data.get("version", version),
|
|
566
|
+
platform=data.get("platform", self.type.value),
|
|
567
|
+
)
|
|
568
|
+
|
|
434
569
|
async def start_keepalive(self, interval: float = 30.0) -> None:
|
|
435
570
|
"""Start a background OS thread that pings the gateway every `interval` seconds.
|
|
436
571
|
|
|
@@ -549,7 +684,7 @@ class AsyncIOSSandbox(AsyncSandbox):
|
|
|
549
684
|
self.environment = AsyncEnvironment(http, self._prefix)
|
|
550
685
|
|
|
551
686
|
|
|
552
|
-
class AsyncWindowsSandbox(_NoAxeAccessibility, AsyncSandbox):
|
|
687
|
+
class AsyncWindowsSandbox(_AsyncSeeding, _NoAxeAccessibility, AsyncSandbox):
|
|
553
688
|
"""Async Windows VM sandbox — see WindowsSandbox."""
|
|
554
689
|
|
|
555
690
|
_accessibility_sandbox_type = "windows"
|
|
@@ -605,7 +740,7 @@ class AsyncWindowsSandbox(_NoAxeAccessibility, AsyncSandbox):
|
|
|
605
740
|
return resp.json()
|
|
606
741
|
|
|
607
742
|
|
|
608
|
-
class AsyncUbuntuSandbox(_NoAxeAccessibility, AsyncSandbox):
|
|
743
|
+
class AsyncUbuntuSandbox(_AsyncSeeding, _NoAxeAccessibility, AsyncSandbox):
|
|
609
744
|
"""Async Ubuntu desktop VM sandbox — see UbuntuSandbox."""
|
|
610
745
|
|
|
611
746
|
_accessibility_sandbox_type = "ubuntu"
|
|
@@ -10,7 +10,7 @@ ShellKind = Literal["bash", "sh", "powershell", "cmd"]
|
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class Shell:
|
|
13
|
-
"""Run commands inside a
|
|
13
|
+
"""Run commands inside a KVM desktop sandbox via the in-guest agent."""
|
|
14
14
|
|
|
15
15
|
def __init__(self, http: httpx.Client, prefix: str, default_shell: ShellKind = "powershell"):
|
|
16
16
|
self._http = http
|
|
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.24 → use_computer-0.0.26}/use_computer/tasks/templates/test_ios_nograder.sh
RENAMED
|
File without changes
|
|
File without changes
|
{use_computer-0.0.24 → use_computer-0.0.26}/use_computer/tasks/templates/test_macos_check.sh
RENAMED
|
File without changes
|
{use_computer-0.0.24 → use_computer-0.0.26}/use_computer/tasks/templates/test_macos_nograder.sh
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|