cua-computer 0.2.7__tar.gz → 0.2.8__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 (29) hide show
  1. {cua_computer-0.2.7 → cua_computer-0.2.8}/PKG-INFO +1 -1
  2. {cua_computer-0.2.7 → cua_computer-0.2.8}/computer/computer.py +22 -1
  3. cua_computer-0.2.8/computer/diorama_computer.py +93 -0
  4. {cua_computer-0.2.7 → cua_computer-0.2.8}/computer/interface/base.py +1 -1
  5. {cua_computer-0.2.7 → cua_computer-0.2.8}/computer/interface/macos.py +5 -1
  6. {cua_computer-0.2.7 → cua_computer-0.2.8}/pyproject.toml +3 -3
  7. {cua_computer-0.2.7 → cua_computer-0.2.8}/README.md +0 -0
  8. {cua_computer-0.2.7 → cua_computer-0.2.8}/computer/__init__.py +0 -0
  9. {cua_computer-0.2.7 → cua_computer-0.2.8}/computer/interface/__init__.py +0 -0
  10. {cua_computer-0.2.7 → cua_computer-0.2.8}/computer/interface/factory.py +0 -0
  11. {cua_computer-0.2.7 → cua_computer-0.2.8}/computer/interface/linux.py +0 -0
  12. {cua_computer-0.2.7 → cua_computer-0.2.8}/computer/interface/models.py +0 -0
  13. {cua_computer-0.2.7 → cua_computer-0.2.8}/computer/logger.py +0 -0
  14. {cua_computer-0.2.7 → cua_computer-0.2.8}/computer/models.py +0 -0
  15. {cua_computer-0.2.7 → cua_computer-0.2.8}/computer/providers/__init__.py +0 -0
  16. {cua_computer-0.2.7 → cua_computer-0.2.8}/computer/providers/base.py +0 -0
  17. {cua_computer-0.2.7 → cua_computer-0.2.8}/computer/providers/cloud/__init__.py +0 -0
  18. {cua_computer-0.2.7 → cua_computer-0.2.8}/computer/providers/cloud/provider.py +0 -0
  19. {cua_computer-0.2.7 → cua_computer-0.2.8}/computer/providers/factory.py +0 -0
  20. {cua_computer-0.2.7 → cua_computer-0.2.8}/computer/providers/lume/__init__.py +0 -0
  21. {cua_computer-0.2.7 → cua_computer-0.2.8}/computer/providers/lume/provider.py +0 -0
  22. {cua_computer-0.2.7 → cua_computer-0.2.8}/computer/providers/lume_api.py +0 -0
  23. {cua_computer-0.2.7 → cua_computer-0.2.8}/computer/providers/lumier/__init__.py +0 -0
  24. {cua_computer-0.2.7 → cua_computer-0.2.8}/computer/providers/lumier/provider.py +0 -0
  25. {cua_computer-0.2.7 → cua_computer-0.2.8}/computer/telemetry.py +0 -0
  26. {cua_computer-0.2.7 → cua_computer-0.2.8}/computer/ui/__init__.py +0 -0
  27. {cua_computer-0.2.7 → cua_computer-0.2.8}/computer/ui/gradio/__init__.py +0 -0
  28. {cua_computer-0.2.7 → cua_computer-0.2.8}/computer/ui/gradio/app.py +0 -0
  29. {cua_computer-0.2.7 → cua_computer-0.2.8}/computer/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cua-computer
3
- Version: 0.2.7
3
+ Version: 0.2.8
4
4
  Summary: Computer-Use Interface (CUI) framework powering Cua
5
5
  Author-Email: TryCua <gh@trycua.com>
6
6
  Requires-Python: >=3.11
@@ -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})
@@ -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.
@@ -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")
@@ -6,7 +6,7 @@ build-backend = "pdm.backend"
6
6
 
7
7
  [project]
8
8
  name = "cua-computer"
9
- version = "0.2.7"
9
+ version = "0.2.8"
10
10
  description = "Computer-Use Interface (CUI) framework powering Cua"
11
11
  readme = "README.md"
12
12
  authors = [
@@ -57,7 +57,7 @@ target-version = [
57
57
 
58
58
  [tool.ruff]
59
59
  line-length = 100
60
- target-version = "0.2.7"
60
+ target-version = "0.2.8"
61
61
  select = [
62
62
  "E",
63
63
  "F",
@@ -71,7 +71,7 @@ docstring-code-format = true
71
71
 
72
72
  [tool.mypy]
73
73
  strict = true
74
- python_version = "0.2.7"
74
+ python_version = "0.2.8"
75
75
  ignore_missing_imports = true
76
76
  disallow_untyped_defs = true
77
77
  check_untyped_defs = true
File without changes