cua-computer 0.2.6__py3-none-any.whl → 0.2.8__py3-none-any.whl
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.
- computer/computer.py +22 -1
- computer/diorama_computer.py +93 -0
- computer/interface/base.py +1 -1
- computer/interface/macos.py +5 -1
- computer/providers/base.py +2 -2
- {cua_computer-0.2.6.dist-info → cua_computer-0.2.8.dist-info}/METADATA +2 -2
- {cua_computer-0.2.6.dist-info → cua_computer-0.2.8.dist-info}/RECORD +9 -8
- {cua_computer-0.2.6.dist-info → cua_computer-0.2.8.dist-info}/WHEEL +0 -0
- {cua_computer-0.2.6.dist-info → cua_computer-0.2.8.dist-info}/entry_points.txt +0 -0
computer/computer.py
CHANGED
@@ -21,6 +21,20 @@ OSType = Literal["macos", "linux", "windows"]
|
|
21
21
|
class Computer:
|
22
22
|
"""Computer is the main class for interacting with the computer."""
|
23
23
|
|
24
|
+
def create_desktop_from_apps(self, apps):
|
25
|
+
"""
|
26
|
+
Create a virtual desktop from a list of app names, returning a DioramaComputer
|
27
|
+
that proxies Diorama.Interface but uses diorama_cmds via the computer interface.
|
28
|
+
|
29
|
+
Args:
|
30
|
+
apps (list[str]): List of application names to include in the desktop.
|
31
|
+
Returns:
|
32
|
+
DioramaComputer: A proxy object with the Diorama interface, but using diorama_cmds.
|
33
|
+
"""
|
34
|
+
assert "app-use" in self.experiments, "App Usage is an experimental feature. Enable it by passing experiments=['app-use'] to Computer()"
|
35
|
+
from .diorama_computer import DioramaComputer
|
36
|
+
return DioramaComputer(self, apps)
|
37
|
+
|
24
38
|
def __init__(
|
25
39
|
self,
|
26
40
|
display: Union[Display, Dict[str, int], str] = "1024x768",
|
@@ -39,7 +53,8 @@ class Computer:
|
|
39
53
|
host: str = os.environ.get("PYLUME_HOST", "localhost"),
|
40
54
|
storage: Optional[str] = None,
|
41
55
|
ephemeral: bool = False,
|
42
|
-
api_key: Optional[str] = None
|
56
|
+
api_key: Optional[str] = None,
|
57
|
+
experiments: Optional[List[str]] = None
|
43
58
|
):
|
44
59
|
"""Initialize a new Computer instance.
|
45
60
|
|
@@ -65,6 +80,8 @@ class Computer:
|
|
65
80
|
host: Host to use for VM provider connections (e.g. "localhost", "host.docker.internal")
|
66
81
|
storage: Optional path for persistent VM storage (Lumier provider)
|
67
82
|
ephemeral: Whether to use ephemeral storage
|
83
|
+
api_key: Optional API key for cloud providers
|
84
|
+
experiments: Optional list of experimental features to enable (e.g. ["app-use"])
|
68
85
|
"""
|
69
86
|
|
70
87
|
self.logger = Logger("cua.computer", verbosity)
|
@@ -80,6 +97,10 @@ class Computer:
|
|
80
97
|
self.ephemeral = ephemeral
|
81
98
|
|
82
99
|
self.api_key = api_key
|
100
|
+
self.experiments = experiments or []
|
101
|
+
|
102
|
+
if "app-use" in self.experiments:
|
103
|
+
assert self.os_type == "macos", "App use experiment is only supported on macOS"
|
83
104
|
|
84
105
|
# The default is currently to use non-ephemeral storage
|
85
106
|
if storage and ephemeral and storage != "ephemeral":
|
@@ -0,0 +1,93 @@
|
|
1
|
+
import asyncio
|
2
|
+
|
3
|
+
class DioramaComputer:
|
4
|
+
"""
|
5
|
+
A Computer-compatible proxy for Diorama that sends commands over the ComputerInterface.
|
6
|
+
"""
|
7
|
+
def __init__(self, computer, apps):
|
8
|
+
self.computer = computer
|
9
|
+
self.apps = apps
|
10
|
+
self.interface = DioramaComputerInterface(computer, apps)
|
11
|
+
self._initialized = False
|
12
|
+
|
13
|
+
async def __aenter__(self):
|
14
|
+
self._initialized = True
|
15
|
+
return self
|
16
|
+
|
17
|
+
async def run(self):
|
18
|
+
if not self._initialized:
|
19
|
+
await self.__aenter__()
|
20
|
+
return self
|
21
|
+
|
22
|
+
class DioramaComputerInterface:
|
23
|
+
"""
|
24
|
+
Diorama Interface proxy that sends diorama_cmds via the Computer's interface.
|
25
|
+
"""
|
26
|
+
def __init__(self, computer, apps):
|
27
|
+
self.computer = computer
|
28
|
+
self.apps = apps
|
29
|
+
self._scene_size = None
|
30
|
+
|
31
|
+
async def _send_cmd(self, action, arguments=None):
|
32
|
+
arguments = arguments or {}
|
33
|
+
arguments = {"app_list": self.apps, **arguments}
|
34
|
+
# Use the computer's interface (must be initialized)
|
35
|
+
iface = getattr(self.computer, "_interface", None)
|
36
|
+
if iface is None:
|
37
|
+
raise RuntimeError("Computer interface not initialized. Call run() first.")
|
38
|
+
result = await iface.diorama_cmd(action, arguments)
|
39
|
+
if not result.get("success"):
|
40
|
+
raise RuntimeError(f"Diorama command failed: {result.get('error')}")
|
41
|
+
return result.get("result")
|
42
|
+
|
43
|
+
async def screenshot(self, as_bytes=True):
|
44
|
+
from PIL import Image
|
45
|
+
import base64
|
46
|
+
result = await self._send_cmd("screenshot")
|
47
|
+
# assume result is a b64 string of an image
|
48
|
+
img_bytes = base64.b64decode(result)
|
49
|
+
import io
|
50
|
+
img = Image.open(io.BytesIO(img_bytes))
|
51
|
+
self._scene_size = img.size
|
52
|
+
return img_bytes if as_bytes else img
|
53
|
+
|
54
|
+
async def get_screen_size(self):
|
55
|
+
if not self._scene_size:
|
56
|
+
await self.screenshot(as_bytes=False)
|
57
|
+
return {"width": self._scene_size[0], "height": self._scene_size[1]}
|
58
|
+
|
59
|
+
async def move_cursor(self, x, y):
|
60
|
+
await self._send_cmd("move_cursor", {"x": x, "y": y})
|
61
|
+
|
62
|
+
async def left_click(self, x=None, y=None):
|
63
|
+
await self._send_cmd("left_click", {"x": x, "y": y})
|
64
|
+
|
65
|
+
async def right_click(self, x=None, y=None):
|
66
|
+
await self._send_cmd("right_click", {"x": x, "y": y})
|
67
|
+
|
68
|
+
async def double_click(self, x=None, y=None):
|
69
|
+
await self._send_cmd("double_click", {"x": x, "y": y})
|
70
|
+
|
71
|
+
async def scroll_up(self, clicks=1):
|
72
|
+
await self._send_cmd("scroll_up", {"clicks": clicks})
|
73
|
+
|
74
|
+
async def scroll_down(self, clicks=1):
|
75
|
+
await self._send_cmd("scroll_down", {"clicks": clicks})
|
76
|
+
|
77
|
+
async def drag_to(self, x, y, duration=0.5):
|
78
|
+
await self._send_cmd("drag_to", {"x": x, "y": y, "duration": duration})
|
79
|
+
|
80
|
+
async def get_cursor_position(self):
|
81
|
+
return await self._send_cmd("get_cursor_position")
|
82
|
+
|
83
|
+
async def type_text(self, text):
|
84
|
+
await self._send_cmd("type_text", {"text": text})
|
85
|
+
|
86
|
+
async def press_key(self, key):
|
87
|
+
await self._send_cmd("press_key", {"key": key})
|
88
|
+
|
89
|
+
async def hotkey(self, *keys):
|
90
|
+
await self._send_cmd("hotkey", {"keys": list(keys)})
|
91
|
+
|
92
|
+
async def to_screen_coordinates(self, x, y):
|
93
|
+
return await self._send_cmd("to_screen_coordinates", {"x": x, "y": y})
|
computer/interface/base.py
CHANGED
@@ -177,7 +177,7 @@ class BaseComputerInterface(ABC):
|
|
177
177
|
async def get_accessibility_tree(self) -> Dict:
|
178
178
|
"""Get the accessibility tree of the current screen."""
|
179
179
|
pass
|
180
|
-
|
180
|
+
|
181
181
|
@abstractmethod
|
182
182
|
async def to_screen_coordinates(self, x: float, y: float) -> tuple[float, float]:
|
183
183
|
"""Convert screenshot coordinates to screen coordinates.
|
computer/interface/macos.py
CHANGED
@@ -346,6 +346,10 @@ class MacOSComputerInterface(BaseComputerInterface):
|
|
346
346
|
asyncio.create_task(self._ws.close())
|
347
347
|
self._ws = None
|
348
348
|
|
349
|
+
async def diorama_cmd(self, action: str, arguments: Optional[dict] = None) -> dict:
|
350
|
+
"""Send a diorama command to the server (macOS only)."""
|
351
|
+
return await self._send_command("diorama_cmd", {"action": action, "arguments": arguments or {}})
|
352
|
+
|
349
353
|
# Mouse Actions
|
350
354
|
async def left_click(self, x: Optional[int] = None, y: Optional[int] = None) -> None:
|
351
355
|
await self._send_command("left_click", {"x": x, "y": y})
|
@@ -568,7 +572,7 @@ class MacOSComputerInterface(BaseComputerInterface):
|
|
568
572
|
if not result.get("success", False):
|
569
573
|
raise RuntimeError(result.get("error", "Failed to get accessibility tree"))
|
570
574
|
return result
|
571
|
-
|
575
|
+
|
572
576
|
async def get_active_window_bounds(self) -> Dict[str, int]:
|
573
577
|
"""Get the bounds of the currently active window."""
|
574
578
|
result = await self._send_command("get_active_window_bounds")
|
computer/providers/base.py
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
"""Base provider interface for VM backends."""
|
2
2
|
|
3
3
|
import abc
|
4
|
-
from enum import
|
4
|
+
from enum import StrEnum
|
5
5
|
from typing import Dict, List, Optional, Any, AsyncContextManager
|
6
6
|
|
7
7
|
|
8
|
-
class VMProviderType(
|
8
|
+
class VMProviderType(StrEnum):
|
9
9
|
"""Enum of supported VM provider types."""
|
10
10
|
LUME = "lume"
|
11
11
|
LUMIER = "lumier"
|
@@ -1,9 +1,9 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: cua-computer
|
3
|
-
Version: 0.2.
|
3
|
+
Version: 0.2.8
|
4
4
|
Summary: Computer-Use Interface (CUI) framework powering Cua
|
5
5
|
Author-Email: TryCua <gh@trycua.com>
|
6
|
-
Requires-Python: >=3.
|
6
|
+
Requires-Python: >=3.11
|
7
7
|
Requires-Dist: pillow>=10.0.0
|
8
8
|
Requires-Dist: websocket-client>=1.8.0
|
9
9
|
Requires-Dist: websockets>=12.0
|
@@ -1,15 +1,16 @@
|
|
1
1
|
computer/__init__.py,sha256=QOxNrrJAuLRnsUC2zIFgRfzVSuDSXiYHlEF-9vkhV0o,1241
|
2
|
-
computer/computer.py,sha256=
|
2
|
+
computer/computer.py,sha256=FtHpNcVjQKNclLlQo5Idt2tBYnuf0417nzVjDMGTRxk,33306
|
3
|
+
computer/diorama_computer.py,sha256=tOzTCTyARD38Eeoc8gjF1rF3eo0kYCkV-70bsxsKAZE,3297
|
3
4
|
computer/interface/__init__.py,sha256=xQvYjq5PMn9ZJOmRR5mWtONTl_0HVd8ACvW6AQnzDdw,262
|
4
|
-
computer/interface/base.py,sha256=
|
5
|
+
computer/interface/base.py,sha256=wmLBiX7rB8cG2Q4fmchdKpjralktzicuYhAh6fDIeqw,6025
|
5
6
|
computer/interface/factory.py,sha256=RjAZAB_jFuS8JierYjLbapRX6RqFE0qE3BiIyP5UDOE,1441
|
6
7
|
computer/interface/linux.py,sha256=CT1N0QA52TNKBbFG2LXdN6yAGWWJ12_2hTMEI8yNoM4,26865
|
7
|
-
computer/interface/macos.py,sha256=
|
8
|
+
computer/interface/macos.py,sha256=SZh3CB_Co9y5xPVfPoo1yIXFqAyDoRSx5nEXNN1od1I,27340
|
8
9
|
computer/interface/models.py,sha256=RZKVUdwKrKUoFqwlx2Dk8Egkmq_AInlIu_d0xg7SZzw,3238
|
9
10
|
computer/logger.py,sha256=UVvnmZGOWVF9TCsixEbeQnDZ3wBPAJ2anW3Zp-MoJ8Y,2896
|
10
11
|
computer/models.py,sha256=iFNM1QfZArD8uf66XJXb2EDIREsfrxqqA5_liLBMfrE,1188
|
11
12
|
computer/providers/__init__.py,sha256=hS9lLxmmHa1u82XJJ_xuqSKipClsYUEPx-8OK9ogtVg,194
|
12
|
-
computer/providers/base.py,sha256=
|
13
|
+
computer/providers/base.py,sha256=J_9r6pJsvGAFDRl56jog_atN7e8uzrvlCQEdRRqye_U,3624
|
13
14
|
computer/providers/cloud/__init__.py,sha256=SDAcfhI2BlmVBrBZOHxQd3i1bJZjMIfl7QgmqjXa4z8,144
|
14
15
|
computer/providers/cloud/provider.py,sha256=gpBl_ZVbwk-0FhYycne-69KslnrAoDSZcyzetpLfiKE,2864
|
15
16
|
computer/providers/factory.py,sha256=9qVdt-fIovSNOokGMZ_2B1VPCLSZeDky4edcXyelZy4,4616
|
@@ -23,7 +24,7 @@ computer/ui/__init__.py,sha256=pmo05ek9qiB_x7DPeE6Vf_8RsIOqTD0w1dBLMHfoOnY,45
|
|
23
24
|
computer/ui/gradio/__init__.py,sha256=5_KimixM48-X74FCsLw7LbSt39MQfUMEL8-M9amK3Cw,117
|
24
25
|
computer/ui/gradio/app.py,sha256=o31nphBcb6zM5OKPuODTjuOzSJ3lt61kQHpUeMBBs70,65077
|
25
26
|
computer/utils.py,sha256=zY50NXB7r51GNLQ6l7lhG_qv0_ufpQ8n0-SDhCei8m4,2838
|
26
|
-
cua_computer-0.2.
|
27
|
-
cua_computer-0.2.
|
28
|
-
cua_computer-0.2.
|
29
|
-
cua_computer-0.2.
|
27
|
+
cua_computer-0.2.8.dist-info/METADATA,sha256=DU7TQmx6VicwaYyE6faehHUy6oLweVKJ1nGBOctOSGY,5844
|
28
|
+
cua_computer-0.2.8.dist-info/WHEEL,sha256=tSfRZzRHthuv7vxpI4aehrdN9scLjk-dCJkPLzkHxGg,90
|
29
|
+
cua_computer-0.2.8.dist-info/entry_points.txt,sha256=6OYgBcLyFCUgeqLgnvMyOJxPCWzgy7se4rLPKtNonMs,34
|
30
|
+
cua_computer-0.2.8.dist-info/RECORD,,
|
File without changes
|
File without changes
|