cua-computer 0.2.2__py3-none-any.whl → 0.2.3__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 CHANGED
@@ -38,7 +38,8 @@ class Computer:
38
38
  noVNC_port: Optional[int] = 8006,
39
39
  host: str = os.environ.get("PYLUME_HOST", "localhost"),
40
40
  storage: Optional[str] = None,
41
- ephemeral: bool = False
41
+ ephemeral: bool = False,
42
+ api_key: Optional[str] = None
42
43
  ):
43
44
  """Initialize a new Computer instance.
44
45
 
@@ -77,6 +78,8 @@ class Computer:
77
78
  self.os_type = os_type
78
79
  self.provider_type = provider_type
79
80
  self.ephemeral = ephemeral
81
+
82
+ self.api_key = api_key
80
83
 
81
84
  # The default is currently to use non-ephemeral storage
82
85
  if storage and ephemeral and storage != "ephemeral":
@@ -256,9 +259,7 @@ class Computer:
256
259
  elif self.provider_type == VMProviderType.CLOUD:
257
260
  self.config.vm_provider = VMProviderFactory.create_provider(
258
261
  self.provider_type,
259
- port=port,
260
- host=host,
261
- storage=storage,
262
+ api_key=self.api_key,
262
263
  verbose=verbose,
263
264
  )
264
265
  else:
@@ -392,12 +393,25 @@ class Computer:
392
393
  self.logger.info(f"Initializing interface for {self.os_type} at {ip_address}")
393
394
  from .interface.base import BaseComputerInterface
394
395
 
395
- self._interface = cast(
396
- BaseComputerInterface,
397
- InterfaceFactory.create_interface_for_os(
398
- os=self.os_type, ip_address=ip_address # type: ignore[arg-type]
399
- ),
400
- )
396
+ # Pass authentication credentials if using cloud provider
397
+ if self.provider_type == VMProviderType.CLOUD and self.api_key and self.config.name:
398
+ self._interface = cast(
399
+ BaseComputerInterface,
400
+ InterfaceFactory.create_interface_for_os(
401
+ os=self.os_type,
402
+ ip_address=ip_address,
403
+ api_key=self.api_key,
404
+ vm_name=self.config.name
405
+ ),
406
+ )
407
+ else:
408
+ self._interface = cast(
409
+ BaseComputerInterface,
410
+ InterfaceFactory.create_interface_for_os(
411
+ os=self.os_type,
412
+ ip_address=ip_address
413
+ ),
414
+ )
401
415
 
402
416
  # Wait for the WebSocket interface to be ready
403
417
  self.logger.info("Connecting to WebSocket interface...")
@@ -492,6 +506,11 @@ class Computer:
492
506
 
493
507
  # Call the provider's get_ip method which will wait indefinitely
494
508
  storage_param = "ephemeral" if self.ephemeral else self.storage
509
+
510
+ # Log the image being used
511
+ self.logger.info(f"Running VM using image: {self.image}")
512
+
513
+ # Call provider.get_ip with explicit image parameter
495
514
  ip = await self.config.vm_provider.get_ip(
496
515
  name=self.config.name,
497
516
  storage=storage_param,
@@ -8,17 +8,21 @@ from ..logger import Logger, LogLevel
8
8
  class BaseComputerInterface(ABC):
9
9
  """Base class for computer control interfaces."""
10
10
 
11
- def __init__(self, ip_address: str, username: str = "lume", password: str = "lume"):
11
+ def __init__(self, ip_address: str, username: str = "lume", password: str = "lume", api_key: Optional[str] = None, vm_name: Optional[str] = None):
12
12
  """Initialize interface.
13
13
 
14
14
  Args:
15
15
  ip_address: IP address of the computer to control
16
16
  username: Username for authentication
17
17
  password: Password for authentication
18
+ api_key: Optional API key for cloud authentication
19
+ vm_name: Optional VM name for cloud authentication
18
20
  """
19
21
  self.ip_address = ip_address
20
22
  self.username = username
21
23
  self.password = password
24
+ self.api_key = api_key
25
+ self.vm_name = vm_name
22
26
  self.logger = Logger("cua.interface", LogLevel.NORMAL)
23
27
 
24
28
  @abstractmethod
@@ -1,6 +1,6 @@
1
1
  """Factory for creating computer interfaces."""
2
2
 
3
- from typing import Literal
3
+ from typing import Literal, Optional
4
4
  from .base import BaseComputerInterface
5
5
 
6
6
  class InterfaceFactory:
@@ -9,13 +9,17 @@ class InterfaceFactory:
9
9
  @staticmethod
10
10
  def create_interface_for_os(
11
11
  os: Literal['macos', 'linux'],
12
- ip_address: str
12
+ ip_address: str,
13
+ api_key: Optional[str] = None,
14
+ vm_name: Optional[str] = None
13
15
  ) -> BaseComputerInterface:
14
16
  """Create an interface for the specified OS.
15
17
 
16
18
  Args:
17
19
  os: Operating system type ('macos' or 'linux')
18
20
  ip_address: IP address of the computer to control
21
+ api_key: Optional API key for cloud authentication
22
+ vm_name: Optional VM name for cloud authentication
19
23
 
20
24
  Returns:
21
25
  BaseComputerInterface: The appropriate interface for the OS
@@ -28,8 +32,8 @@ class InterfaceFactory:
28
32
  from .linux import LinuxComputerInterface
29
33
 
30
34
  if os == 'macos':
31
- return MacOSComputerInterface(ip_address)
35
+ return MacOSComputerInterface(ip_address, api_key=api_key, vm_name=vm_name)
32
36
  elif os == 'linux':
33
- return LinuxComputerInterface(ip_address)
37
+ return LinuxComputerInterface(ip_address, api_key=api_key, vm_name=vm_name)
34
38
  else:
35
39
  raise ValueError(f"Unsupported OS type: {os}")
@@ -15,8 +15,8 @@ from .models import Key, KeyType
15
15
  class LinuxComputerInterface(BaseComputerInterface):
16
16
  """Interface for Linux."""
17
17
 
18
- def __init__(self, ip_address: str, username: str = "lume", password: str = "lume"):
19
- super().__init__(ip_address, username, password)
18
+ def __init__(self, ip_address: str, username: str = "lume", password: str = "lume", api_key: Optional[str] = None, vm_name: Optional[str] = None):
19
+ super().__init__(ip_address, username, password, api_key, vm_name)
20
20
  self._ws = None
21
21
  self._reconnect_task = None
22
22
  self._closed = False
@@ -37,7 +37,9 @@ class LinuxComputerInterface(BaseComputerInterface):
37
37
  Returns:
38
38
  WebSocket URI for the Computer API Server
39
39
  """
40
- return f"ws://{self.ip_address}:8000/ws"
40
+ protocol = "wss" if self.api_key else "ws"
41
+ port = "8443" if self.api_key else "8000"
42
+ return f"{protocol}://{self.ip_address}:{port}/ws"
41
43
 
42
44
  async def _keep_alive(self):
43
45
  """Keep the WebSocket connection alive with automatic reconnection."""
@@ -86,6 +88,32 @@ class LinuxComputerInterface(BaseComputerInterface):
86
88
  timeout=30,
87
89
  )
88
90
  self.logger.info("WebSocket connection established")
91
+
92
+ # If api_key and vm_name are provided, perform authentication handshake
93
+ if self.api_key and self.vm_name:
94
+ self.logger.info("Performing authentication handshake...")
95
+ auth_message = {
96
+ "command": "authenticate",
97
+ "params": {
98
+ "api_key": self.api_key,
99
+ "container_name": self.vm_name
100
+ }
101
+ }
102
+ await self._ws.send(json.dumps(auth_message))
103
+
104
+ # Wait for authentication response
105
+ auth_response = await asyncio.wait_for(self._ws.recv(), timeout=10)
106
+ auth_result = json.loads(auth_response)
107
+
108
+ if not auth_result.get("success"):
109
+ error_msg = auth_result.get("error", "Authentication failed")
110
+ self.logger.error(f"Authentication failed: {error_msg}")
111
+ await self._ws.close()
112
+ self._ws = None
113
+ raise ConnectionError(f"Authentication failed: {error_msg}")
114
+
115
+ self.logger.info("Authentication successful")
116
+
89
117
  self._reconnect_delay = 1 # Reset reconnect delay on successful connection
90
118
  self._last_ping = time.time()
91
119
  retry_count = 0 # Reset retry count on successful connection
@@ -13,10 +13,10 @@ from .models import Key, KeyType
13
13
 
14
14
 
15
15
  class MacOSComputerInterface(BaseComputerInterface):
16
- """Interface for MacOS."""
16
+ """Interface for macOS."""
17
17
 
18
- def __init__(self, ip_address: str, username: str = "lume", password: str = "lume"):
19
- super().__init__(ip_address, username, password)
18
+ def __init__(self, ip_address: str, username: str = "lume", password: str = "lume", api_key: Optional[str] = None, vm_name: Optional[str] = None):
19
+ super().__init__(ip_address, username, password, api_key, vm_name)
20
20
  self._ws = None
21
21
  self._reconnect_task = None
22
22
  self._closed = False
@@ -27,7 +27,7 @@ class MacOSComputerInterface(BaseComputerInterface):
27
27
  self._max_reconnect_delay = 30 # Maximum delay between reconnection attempts
28
28
  self._log_connection_attempts = True # Flag to control connection attempt logging
29
29
 
30
- # Set logger name for MacOS interface
30
+ # Set logger name for macOS interface
31
31
  self.logger = Logger("cua.interface.macos", LogLevel.NORMAL)
32
32
 
33
33
  @property
@@ -37,7 +37,9 @@ class MacOSComputerInterface(BaseComputerInterface):
37
37
  Returns:
38
38
  WebSocket URI for the Computer API Server
39
39
  """
40
- return f"ws://{self.ip_address}:8000/ws"
40
+ protocol = "wss" if self.api_key else "ws"
41
+ port = "8443" if self.api_key else "8000"
42
+ return f"{protocol}://{self.ip_address}:{port}/ws"
41
43
 
42
44
  async def _keep_alive(self):
43
45
  """Keep the WebSocket connection alive with automatic reconnection."""
@@ -86,6 +88,32 @@ class MacOSComputerInterface(BaseComputerInterface):
86
88
  timeout=30,
87
89
  )
88
90
  self.logger.info("WebSocket connection established")
91
+
92
+ # If api_key and vm_name are provided, perform authentication handshake
93
+ if self.api_key and self.vm_name:
94
+ self.logger.info("Performing authentication handshake...")
95
+ auth_message = {
96
+ "command": "authenticate",
97
+ "params": {
98
+ "api_key": self.api_key,
99
+ "container_name": self.vm_name
100
+ }
101
+ }
102
+ await self._ws.send(json.dumps(auth_message))
103
+
104
+ # Wait for authentication response
105
+ auth_response = await asyncio.wait_for(self._ws.recv(), timeout=10)
106
+ auth_result = json.loads(auth_response)
107
+
108
+ if not auth_result.get("success"):
109
+ error_msg = auth_result.get("error", "Authentication failed")
110
+ self.logger.error(f"Authentication failed: {error_msg}")
111
+ await self._ws.close()
112
+ self._ws = None
113
+ raise ConnectionError(f"Authentication failed: {error_msg}")
114
+
115
+ self.logger.info("Authentication successful")
116
+
89
117
  self._reconnect_delay = 1 # Reset reconnect delay on successful connection
90
118
  self._last_ping = time.time()
91
119
  retry_count = 0 # Reset retry count on successful connection
@@ -11,90 +11,65 @@ from ..base import BaseVMProvider, VMProviderType
11
11
  # Setup logging
12
12
  logger = logging.getLogger(__name__)
13
13
 
14
+ import asyncio
15
+ import aiohttp
16
+ from urllib.parse import urlparse
17
+
14
18
  class CloudProvider(BaseVMProvider):
15
- """Cloud VM Provider stub implementation.
16
-
17
- This is a placeholder for a future cloud VM provider implementation.
18
- """
19
-
19
+ """Cloud VM Provider implementation."""
20
20
  def __init__(
21
- self,
22
- host: str = "localhost",
23
- port: int = 7777,
24
- storage: Optional[str] = None,
21
+ self,
22
+ api_key: str,
25
23
  verbose: bool = False,
24
+ **kwargs,
26
25
  ):
27
- """Initialize the Cloud provider.
28
-
26
+ """
29
27
  Args:
30
- host: Host to use for API connections (default: localhost)
31
- port: Port for the API server (default: 7777)
32
- storage: Path to store VM data
28
+ api_key: API key for authentication
29
+ name: Name of the VM
33
30
  verbose: Enable verbose logging
34
31
  """
35
- self.host = host
36
- self.port = port
37
- self.storage = storage
32
+ assert api_key, "api_key required for CloudProvider"
33
+ self.api_key = api_key
38
34
  self.verbose = verbose
39
-
40
- logger.warning("CloudProvider is not yet implemented")
41
-
35
+
42
36
  @property
43
37
  def provider_type(self) -> VMProviderType:
44
- """Get the provider type."""
45
38
  return VMProviderType.CLOUD
46
-
39
+
47
40
  async def __aenter__(self):
48
- """Enter async context manager."""
49
- logger.debug("Entering CloudProvider context")
50
41
  return self
51
-
42
+
52
43
  async def __aexit__(self, exc_type, exc_val, exc_tb):
53
- """Exit async context manager."""
54
- logger.debug("Exiting CloudProvider context")
55
-
44
+ pass
45
+
56
46
  async def get_vm(self, name: str, storage: Optional[str] = None) -> Dict[str, Any]:
57
- """Get VM information by name."""
58
- logger.warning("CloudProvider.get_vm is not implemented")
59
- return {
60
- "name": name,
61
- "status": "unavailable",
62
- "message": "CloudProvider is not implemented"
63
- }
64
-
47
+ """Get VM VNC URL by name using the cloud API."""
48
+ return {"name": name, "hostname": f"{name}.containers.cloud.trycua.com"}
49
+
65
50
  async def list_vms(self) -> List[Dict[str, Any]]:
66
- """List all available VMs."""
67
51
  logger.warning("CloudProvider.list_vms is not implemented")
68
52
  return []
69
-
53
+
70
54
  async def run_vm(self, image: str, name: str, run_opts: Dict[str, Any], storage: Optional[str] = None) -> Dict[str, Any]:
71
- """Run a VM with the given options."""
72
55
  logger.warning("CloudProvider.run_vm is not implemented")
73
- return {
74
- "name": name,
75
- "status": "unavailable",
76
- "message": "CloudProvider is not implemented"
77
- }
78
-
56
+ return {"name": name, "status": "unavailable", "message": "CloudProvider is not implemented"}
57
+
79
58
  async def stop_vm(self, name: str, storage: Optional[str] = None) -> Dict[str, Any]:
80
- """Stop a running VM."""
81
59
  logger.warning("CloudProvider.stop_vm is not implemented")
82
- return {
83
- "name": name,
84
- "status": "stopped",
85
- "message": "CloudProvider is not implemented"
86
- }
87
-
60
+ return {"name": name, "status": "stopped", "message": "CloudProvider is not implemented"}
61
+
88
62
  async def update_vm(self, name: str, update_opts: Dict[str, Any], storage: Optional[str] = None) -> Dict[str, Any]:
89
- """Update VM configuration."""
90
63
  logger.warning("CloudProvider.update_vm is not implemented")
91
- return {
92
- "name": name,
93
- "status": "unchanged",
94
- "message": "CloudProvider is not implemented"
95
- }
96
-
97
- async def get_ip(self, name: str, storage: Optional[str] = None, retry_delay: int = 2) -> str:
98
- """Get the IP address of a VM."""
99
- logger.warning("CloudProvider.get_ip is not implemented")
100
- raise NotImplementedError("CloudProvider.get_ip is not implemented")
64
+ return {"name": name, "status": "unchanged", "message": "CloudProvider is not implemented"}
65
+
66
+ async def get_ip(self, name: Optional[str] = None, storage: Optional[str] = None, retry_delay: int = 2) -> str:
67
+ """
68
+ Return the VM's IP address as '{container_name}.containers.cloud.trycua.com'.
69
+ Uses the provided 'name' argument (the VM name requested by the caller),
70
+ falling back to self.name only if 'name' is None.
71
+ Retries up to 3 times with retry_delay seconds if hostname is not available.
72
+ """
73
+ if name is None:
74
+ raise ValueError("VM name is required for CloudProvider.get_ip")
75
+ return f"{name}.containers.cloud.trycua.com"
@@ -22,7 +22,8 @@ class VMProviderFactory:
22
22
  image: Optional[str] = None,
23
23
  verbose: bool = False,
24
24
  ephemeral: bool = False,
25
- noVNC_port: Optional[int] = None
25
+ noVNC_port: Optional[int] = None,
26
+ **kwargs,
26
27
  ) -> BaseVMProvider:
27
28
  """Create a VM provider of the specified type.
28
29
 
@@ -101,12 +102,9 @@ class VMProviderFactory:
101
102
  elif provider_type == VMProviderType.CLOUD:
102
103
  try:
103
104
  from .cloud import CloudProvider
104
- # Return the stub implementation of CloudProvider
105
105
  return CloudProvider(
106
- host=host,
107
- port=port,
108
- storage=storage,
109
- verbose=verbose
106
+ verbose=verbose,
107
+ **kwargs,
110
108
  )
111
109
  except ImportError as e:
112
110
  logger.error(f"Failed to import CloudProvider: {e}")
@@ -344,9 +344,15 @@ class LumierProvider(BaseVMProvider):
344
344
  # Use the VM image passed from the Computer class
345
345
  print(f"Using VM image: {self.image}")
346
346
 
347
+ # If ghcr.io is in the image, use the full image name
348
+ if "ghcr.io" in self.image:
349
+ vm_image = self.image
350
+ else:
351
+ vm_image = f"ghcr.io/trycua/{self.image}"
352
+
347
353
  cmd.extend([
348
354
  "-e", f"VM_NAME={self.container_name}",
349
- "-e", f"VERSION=ghcr.io/trycua/{self.image}",
355
+ "-e", f"VERSION={vm_image}",
350
356
  "-e", f"CPU_CORES={run_opts.get('cpu', '4')}",
351
357
  "-e", f"RAM_SIZE={memory_mb}",
352
358
  ])
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cua-computer
3
- Version: 0.2.2
3
+ Version: 0.2.3
4
4
  Summary: Computer-Use Interface (CUI) framework powering Cua
5
5
  Author-Email: TryCua <gh@trycua.com>
6
6
  Requires-Python: >=3.10
@@ -1,29 +1,29 @@
1
1
  computer/__init__.py,sha256=QOxNrrJAuLRnsUC2zIFgRfzVSuDSXiYHlEF-9vkhV0o,1241
2
- computer/computer.py,sha256=nv2ZYg00ayq6N2OVVouE3ZekByCoYQez6vmuntVvKLs,31470
2
+ computer/computer.py,sha256=Rc32XFZdKr7XZKO0zhbEom-REvYYPPlvmvjDbw5gP9k,32218
3
3
  computer/interface/__init__.py,sha256=xQvYjq5PMn9ZJOmRR5mWtONTl_0HVd8ACvW6AQnzDdw,262
4
- computer/interface/base.py,sha256=uRF2AfF3dbpOmvzZ55JgODpNC6LiudfhK8KCf5v7uUw,5771
5
- computer/interface/factory.py,sha256=2zUgFTKbhGqDbFKAcpOu47sDYmi4qidHegSQbfEAg1A,1159
6
- computer/interface/linux.py,sha256=5CyC0o-XR-TcelB5MPdzL20qSrT0C8kFYJxDupY3MWI,25350
7
- computer/interface/macos.py,sha256=WiK8hkJJwb6E6hFOMO30HVs6S2qSsPMM6f47cFj19OQ,25350
4
+ computer/interface/base.py,sha256=CD9WpDp-6qP-ID5MjhXA8qpYs0XhJ4TPkR917l2FFSo,6021
5
+ computer/interface/factory.py,sha256=RjAZAB_jFuS8JierYjLbapRX6RqFE0qE3BiIyP5UDOE,1441
6
+ computer/interface/linux.py,sha256=EIVxwD_q0OmZoaW0Tv8FvKTpja9kInIsKWI47gpn2Po,27077
7
+ computer/interface/macos.py,sha256=_8R_IroxbcVmh1WagrjDQOitaT6tVkCHVzGgA_lwTrM,27077
8
8
  computer/interface/models.py,sha256=RZKVUdwKrKUoFqwlx2Dk8Egkmq_AInlIu_d0xg7SZzw,3238
9
9
  computer/logger.py,sha256=UVvnmZGOWVF9TCsixEbeQnDZ3wBPAJ2anW3Zp-MoJ8Y,2896
10
10
  computer/models.py,sha256=iFNM1QfZArD8uf66XJXb2EDIREsfrxqqA5_liLBMfrE,1188
11
11
  computer/providers/__init__.py,sha256=hS9lLxmmHa1u82XJJ_xuqSKipClsYUEPx-8OK9ogtVg,194
12
12
  computer/providers/base.py,sha256=pUrM5aVBSUQS2TbfoiyMkNz20dWN7-F_aEjSf8EiJRc,3623
13
13
  computer/providers/cloud/__init__.py,sha256=SDAcfhI2BlmVBrBZOHxQd3i1bJZjMIfl7QgmqjXa4z8,144
14
- computer/providers/cloud/provider.py,sha256=Cgjb7vFMk4OxG8TKOMYWO8v3v1s7WtWXppNwQRm97Cw,3523
15
- computer/providers/factory.py,sha256=FskJGkdhl_arlGr2wd_e4Adi_L14VTkvPTK2gTuUhT0,4731
14
+ computer/providers/cloud/provider.py,sha256=gpBl_ZVbwk-0FhYycne-69KslnrAoDSZcyzetpLfiKE,2864
15
+ computer/providers/factory.py,sha256=9qVdt-fIovSNOokGMZ_2B1VPCLSZeDky4edcXyelZy4,4616
16
16
  computer/providers/lume/__init__.py,sha256=E6hTbVQF5lLZD8JyG4rTwUnCBO4q9K8UkYNQ31R0h7c,193
17
17
  computer/providers/lume/provider.py,sha256=grLZeXd4Y8iYsNq2gfNGcQq1bnTcNYNepEv-mxmROG4,20562
18
18
  computer/providers/lume_api.py,sha256=qLYFYdWtWVxWjMq8baiqlIW2EUaen4Gl2Tc1Qr_QEig,20196
19
19
  computer/providers/lumier/__init__.py,sha256=qz8coMA2K5MVoqNC12SDXJe6lI7z2pn6RHssUOMY5Ug,212
20
- computer/providers/lumier/provider.py,sha256=qBhaI_AFeS2ksLp8gHpHyts_HHSG8TduT6dPfBQ29hA,46547
20
+ computer/providers/lumier/provider.py,sha256=CXwAKwJfR9ALFGM5u7UIZ-YrFwPvew_01wTe7dVmVbQ,46751
21
21
  computer/telemetry.py,sha256=FvNFpxgeRuCMdNpREuSL7bOMZy9gSzY4J0rLeNDw0CU,3746
22
22
  computer/ui/__init__.py,sha256=pmo05ek9qiB_x7DPeE6Vf_8RsIOqTD0w1dBLMHfoOnY,45
23
23
  computer/ui/gradio/__init__.py,sha256=5_KimixM48-X74FCsLw7LbSt39MQfUMEL8-M9amK3Cw,117
24
24
  computer/ui/gradio/app.py,sha256=o31nphBcb6zM5OKPuODTjuOzSJ3lt61kQHpUeMBBs70,65077
25
25
  computer/utils.py,sha256=zY50NXB7r51GNLQ6l7lhG_qv0_ufpQ8n0-SDhCei8m4,2838
26
- cua_computer-0.2.2.dist-info/METADATA,sha256=_uIqSLZnFdrSRDunf0AGhE93Twq91QbGzlzh3wWgwDc,5844
27
- cua_computer-0.2.2.dist-info/WHEEL,sha256=tSfRZzRHthuv7vxpI4aehrdN9scLjk-dCJkPLzkHxGg,90
28
- cua_computer-0.2.2.dist-info/entry_points.txt,sha256=6OYgBcLyFCUgeqLgnvMyOJxPCWzgy7se4rLPKtNonMs,34
29
- cua_computer-0.2.2.dist-info/RECORD,,
26
+ cua_computer-0.2.3.dist-info/METADATA,sha256=zStwBXVos0iY6yfYVYeKg9ogTELmf3BW8_i_a1EYWg0,5844
27
+ cua_computer-0.2.3.dist-info/WHEEL,sha256=tSfRZzRHthuv7vxpI4aehrdN9scLjk-dCJkPLzkHxGg,90
28
+ cua_computer-0.2.3.dist-info/entry_points.txt,sha256=6OYgBcLyFCUgeqLgnvMyOJxPCWzgy7se4rLPKtNonMs,34
29
+ cua_computer-0.2.3.dist-info/RECORD,,