orgo 0.0.24__py3-none-any.whl → 0.0.26__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.
- orgo/__init__.py +2 -1
- orgo/api/client.py +94 -68
- orgo/computer.py +186 -187
- orgo/project.py +78 -55
- {orgo-0.0.24.dist-info → orgo-0.0.26.dist-info}/METADATA +1 -1
- orgo-0.0.26.dist-info/RECORD +12 -0
- orgo-0.0.24.dist-info/RECORD +0 -12
- {orgo-0.0.24.dist-info → orgo-0.0.26.dist-info}/WHEEL +0 -0
- {orgo-0.0.24.dist-info → orgo-0.0.26.dist-info}/top_level.txt +0 -0
orgo/__init__.py
CHANGED
orgo/api/client.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""API client for Orgo service"""
|
|
2
2
|
|
|
3
3
|
import requests
|
|
4
|
-
from typing import Dict, Any, Optional
|
|
4
|
+
from typing import Dict, Any, Optional, List
|
|
5
5
|
|
|
6
6
|
from orgo.utils.auth import get_api_key
|
|
7
7
|
|
|
@@ -39,73 +39,99 @@ class ApiClient:
|
|
|
39
39
|
raise Exception(error_message) from e
|
|
40
40
|
raise Exception(f"Connection error: {str(e)}") from e
|
|
41
41
|
|
|
42
|
-
#
|
|
43
|
-
def
|
|
44
|
-
"""Create a new project
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
42
|
+
# Project methods
|
|
43
|
+
def create_project(self, name: str) -> Dict[str, Any]:
|
|
44
|
+
"""Create a new named project"""
|
|
45
|
+
return self._request("POST", "projects", {"name": name})
|
|
46
|
+
|
|
47
|
+
def get_project_by_name(self, name: str) -> Dict[str, Any]:
|
|
48
|
+
"""Get project details by name"""
|
|
49
|
+
return self._request("GET", f"projects/by-name/{name}")
|
|
50
|
+
|
|
51
|
+
def get_project(self, project_id: str) -> Dict[str, Any]:
|
|
52
|
+
"""Get project details by ID"""
|
|
53
|
+
return self._request("GET", f"projects/{project_id}")
|
|
54
|
+
|
|
55
|
+
def list_projects(self) -> List[Dict[str, Any]]:
|
|
56
|
+
"""List all projects"""
|
|
57
|
+
response = self._request("GET", "projects")
|
|
58
|
+
return response.get("projects", [])
|
|
59
|
+
|
|
60
|
+
def start_project(self, project_id: str) -> Dict[str, Any]:
|
|
61
|
+
"""Start a project"""
|
|
62
62
|
return self._request("POST", f"projects/{project_id}/start")
|
|
63
63
|
|
|
64
|
-
def
|
|
65
|
-
|
|
66
|
-
project = self.get_status(project_name)
|
|
67
|
-
project_id = project.get("id")
|
|
68
|
-
if not project_id:
|
|
69
|
-
raise ValueError(f"Could not find ID for project {project_name}")
|
|
64
|
+
def stop_project(self, project_id: str) -> Dict[str, Any]:
|
|
65
|
+
"""Stop a project"""
|
|
70
66
|
return self._request("POST", f"projects/{project_id}/stop")
|
|
71
67
|
|
|
72
|
-
def
|
|
73
|
-
|
|
74
|
-
project = self.get_status(project_name)
|
|
75
|
-
project_id = project.get("id")
|
|
76
|
-
if not project_id:
|
|
77
|
-
raise ValueError(f"Could not find ID for project {project_name}")
|
|
68
|
+
def restart_project(self, project_id: str) -> Dict[str, Any]:
|
|
69
|
+
"""Restart a project"""
|
|
78
70
|
return self._request("POST", f"projects/{project_id}/restart")
|
|
79
71
|
|
|
80
|
-
def
|
|
81
|
-
|
|
82
|
-
project = self.get_status(project_name)
|
|
83
|
-
project_id = project.get("id")
|
|
84
|
-
if not project_id:
|
|
85
|
-
raise ValueError(f"Could not find ID for project {project_name}")
|
|
72
|
+
def delete_project(self, project_id: str) -> Dict[str, Any]:
|
|
73
|
+
"""Delete a project and all its computers"""
|
|
86
74
|
return self._request("POST", f"projects/{project_id}/delete")
|
|
87
75
|
|
|
76
|
+
# Computer methods
|
|
77
|
+
def create_computer(self, project_name: str, computer_name: str,
|
|
78
|
+
os: str = "linux", ram: int = 2, cpu: int = 2,
|
|
79
|
+
gpu: str = "none") -> Dict[str, Any]:
|
|
80
|
+
"""Create a new computer within a project"""
|
|
81
|
+
return self._request("POST", f"projects/{project_name}/computers", {
|
|
82
|
+
"name": computer_name,
|
|
83
|
+
"os": os,
|
|
84
|
+
"ram": ram,
|
|
85
|
+
"cpu": cpu,
|
|
86
|
+
"gpu": gpu
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
def list_computers(self, project_name: str) -> List[Dict[str, Any]]:
|
|
90
|
+
"""List all computers in a project"""
|
|
91
|
+
response = self._request("GET", f"projects/{project_name}/computers")
|
|
92
|
+
return response.get("computers", [])
|
|
93
|
+
|
|
94
|
+
def get_computer(self, computer_id: str) -> Dict[str, Any]:
|
|
95
|
+
"""Get computer details"""
|
|
96
|
+
return self._request("GET", f"computers/{computer_id}")
|
|
97
|
+
|
|
98
|
+
def delete_computer(self, computer_id: str) -> Dict[str, Any]:
|
|
99
|
+
"""Delete a computer"""
|
|
100
|
+
return self._request("DELETE", f"computers/{computer_id}")
|
|
101
|
+
|
|
102
|
+
def start_computer(self, computer_id: str) -> Dict[str, Any]:
|
|
103
|
+
"""Start a computer"""
|
|
104
|
+
return self._request("POST", f"computers/{computer_id}/start")
|
|
105
|
+
|
|
106
|
+
def stop_computer(self, computer_id: str) -> Dict[str, Any]:
|
|
107
|
+
"""Stop a computer"""
|
|
108
|
+
return self._request("POST", f"computers/{computer_id}/stop")
|
|
109
|
+
|
|
110
|
+
def restart_computer(self, computer_id: str) -> Dict[str, Any]:
|
|
111
|
+
"""Restart a computer"""
|
|
112
|
+
return self._request("POST", f"computers/{computer_id}/restart")
|
|
113
|
+
|
|
88
114
|
# Computer control methods
|
|
89
|
-
def left_click(self,
|
|
90
|
-
return self._request("POST", f"computers/{
|
|
115
|
+
def left_click(self, computer_id: str, x: int, y: int) -> Dict[str, Any]:
|
|
116
|
+
return self._request("POST", f"computers/{computer_id}/click", {
|
|
91
117
|
"button": "left", "x": x, "y": y
|
|
92
118
|
})
|
|
93
119
|
|
|
94
|
-
def right_click(self,
|
|
95
|
-
return self._request("POST", f"computers/{
|
|
120
|
+
def right_click(self, computer_id: str, x: int, y: int) -> Dict[str, Any]:
|
|
121
|
+
return self._request("POST", f"computers/{computer_id}/click", {
|
|
96
122
|
"button": "right", "x": x, "y": y
|
|
97
123
|
})
|
|
98
124
|
|
|
99
|
-
def double_click(self,
|
|
100
|
-
return self._request("POST", f"computers/{
|
|
125
|
+
def double_click(self, computer_id: str, x: int, y: int) -> Dict[str, Any]:
|
|
126
|
+
return self._request("POST", f"computers/{computer_id}/click", {
|
|
101
127
|
"button": "left", "x": x, "y": y, "double": True
|
|
102
128
|
})
|
|
103
129
|
|
|
104
|
-
def drag(self,
|
|
130
|
+
def drag(self, computer_id: str, start_x: int, start_y: int,
|
|
105
131
|
end_x: int, end_y: int, button: str = "left",
|
|
106
132
|
duration: float = 0.5) -> Dict[str, Any]:
|
|
107
133
|
"""Perform a drag operation from start to end coordinates"""
|
|
108
|
-
return self._request("POST", f"computers/{
|
|
134
|
+
return self._request("POST", f"computers/{computer_id}/drag", {
|
|
109
135
|
"start_x": start_x,
|
|
110
136
|
"start_y": start_y,
|
|
111
137
|
"end_x": end_x,
|
|
@@ -114,52 +140,52 @@ class ApiClient:
|
|
|
114
140
|
"duration": duration
|
|
115
141
|
})
|
|
116
142
|
|
|
117
|
-
def scroll(self,
|
|
118
|
-
return self._request("POST", f"computers/{
|
|
143
|
+
def scroll(self, computer_id: str, direction: str, amount: int = 3) -> Dict[str, Any]:
|
|
144
|
+
return self._request("POST", f"computers/{computer_id}/scroll", {
|
|
119
145
|
"direction": direction, "amount": amount
|
|
120
146
|
})
|
|
121
147
|
|
|
122
|
-
def type_text(self,
|
|
123
|
-
return self._request("POST", f"computers/{
|
|
148
|
+
def type_text(self, computer_id: str, text: str) -> Dict[str, Any]:
|
|
149
|
+
return self._request("POST", f"computers/{computer_id}/type", {
|
|
124
150
|
"text": text
|
|
125
151
|
})
|
|
126
152
|
|
|
127
|
-
def key_press(self,
|
|
128
|
-
return self._request("POST", f"computers/{
|
|
153
|
+
def key_press(self, computer_id: str, key: str) -> Dict[str, Any]:
|
|
154
|
+
return self._request("POST", f"computers/{computer_id}/key", {
|
|
129
155
|
"key": key
|
|
130
156
|
})
|
|
131
157
|
|
|
132
|
-
def get_screenshot(self,
|
|
133
|
-
return self._request("GET", f"computers/{
|
|
158
|
+
def get_screenshot(self, computer_id: str) -> Dict[str, Any]:
|
|
159
|
+
return self._request("GET", f"computers/{computer_id}/screenshot")
|
|
134
160
|
|
|
135
|
-
def execute_bash(self,
|
|
136
|
-
return self._request("POST", f"computers/{
|
|
161
|
+
def execute_bash(self, computer_id: str, command: str) -> Dict[str, Any]:
|
|
162
|
+
return self._request("POST", f"computers/{computer_id}/bash", {
|
|
137
163
|
"command": command
|
|
138
164
|
})
|
|
139
165
|
|
|
140
|
-
def execute_python(self,
|
|
166
|
+
def execute_python(self, computer_id: str, code: str, timeout: int = 10) -> Dict[str, Any]:
|
|
141
167
|
"""Execute Python code on the computer"""
|
|
142
|
-
return self._request("POST", f"computers/{
|
|
168
|
+
return self._request("POST", f"computers/{computer_id}/exec", {
|
|
143
169
|
"code": code,
|
|
144
170
|
"timeout": timeout
|
|
145
171
|
})
|
|
146
172
|
|
|
147
|
-
def wait(self,
|
|
148
|
-
return self._request("POST", f"computers/{
|
|
149
|
-
"
|
|
173
|
+
def wait(self, computer_id: str, duration: float) -> Dict[str, Any]:
|
|
174
|
+
return self._request("POST", f"computers/{computer_id}/wait", {
|
|
175
|
+
"duration": duration
|
|
150
176
|
})
|
|
151
177
|
|
|
152
178
|
# Streaming methods
|
|
153
|
-
def start_stream(self,
|
|
179
|
+
def start_stream(self, computer_id: str, connection_name: str) -> Dict[str, Any]:
|
|
154
180
|
"""Start streaming to a configured RTMP connection"""
|
|
155
|
-
return self._request("POST", f"computers/{
|
|
181
|
+
return self._request("POST", f"computers/{computer_id}/stream/start", {
|
|
156
182
|
"connection_name": connection_name
|
|
157
183
|
})
|
|
158
184
|
|
|
159
|
-
def stop_stream(self,
|
|
185
|
+
def stop_stream(self, computer_id: str) -> Dict[str, Any]:
|
|
160
186
|
"""Stop the active stream"""
|
|
161
|
-
return self._request("POST", f"computers/{
|
|
187
|
+
return self._request("POST", f"computers/{computer_id}/stream/stop")
|
|
162
188
|
|
|
163
|
-
def get_stream_status(self,
|
|
189
|
+
def get_stream_status(self, computer_id: str) -> Dict[str, Any]:
|
|
164
190
|
"""Get current stream status"""
|
|
165
|
-
return self._request("GET", f"computers/{
|
|
191
|
+
return self._request("GET", f"computers/{computer_id}/stream/status")
|
orgo/computer.py
CHANGED
|
@@ -3,39 +3,52 @@ import os
|
|
|
3
3
|
import io
|
|
4
4
|
import base64
|
|
5
5
|
import logging
|
|
6
|
-
|
|
6
|
+
import uuid
|
|
7
|
+
from typing import Dict, List, Any, Optional, Callable, Literal, Union
|
|
7
8
|
from PIL import Image
|
|
8
9
|
import requests
|
|
9
10
|
from requests.exceptions import RequestException
|
|
10
11
|
|
|
11
12
|
from .api.client import ApiClient
|
|
12
13
|
from .prompt import get_provider
|
|
13
|
-
from .project import ProjectManager
|
|
14
14
|
|
|
15
15
|
logger = logging.getLogger(__name__)
|
|
16
16
|
|
|
17
17
|
class Computer:
|
|
18
18
|
def __init__(self,
|
|
19
|
-
|
|
19
|
+
project: Optional[Union[str, 'Project']] = None,
|
|
20
|
+
name: Optional[str] = None,
|
|
21
|
+
computer_id: Optional[str] = None,
|
|
20
22
|
api_key: Optional[str] = None,
|
|
21
|
-
config: Optional[Dict[str, Any]] = None,
|
|
22
23
|
base_api_url: Optional[str] = None,
|
|
23
|
-
ram: Optional[Literal[2, 4]] = None,
|
|
24
|
-
memory: Optional[Literal[2, 4]] = None,
|
|
25
|
-
cpu: Optional[Literal[2, 4]] = None
|
|
24
|
+
ram: Optional[Literal[1, 2, 4, 8, 16, 32, 64]] = None,
|
|
25
|
+
memory: Optional[Literal[1, 2, 4, 8, 16, 32, 64]] = None,
|
|
26
|
+
cpu: Optional[Literal[1, 2, 4, 8, 16]] = None,
|
|
27
|
+
os: Optional[Literal["linux", "windows"]] = None,
|
|
28
|
+
gpu: Optional[Literal["none", "a10", "l40s", "a100-40gb", "a100-80gb"]] = None):
|
|
26
29
|
"""
|
|
27
30
|
Initialize an Orgo virtual computer.
|
|
28
31
|
|
|
29
32
|
Args:
|
|
30
|
-
|
|
33
|
+
project: Project name (str) or Project instance. If not provided, creates a new project.
|
|
34
|
+
name: Computer name within the project (optional, auto-generated if not provided)
|
|
35
|
+
computer_id: Existing computer ID to connect to (optional)
|
|
31
36
|
api_key: Orgo API key (defaults to ORGO_API_KEY env var)
|
|
32
|
-
config: Configuration for new computer (optional)
|
|
33
37
|
base_api_url: Custom API URL (optional)
|
|
34
|
-
ram: RAM in GB
|
|
35
|
-
|
|
36
|
-
|
|
38
|
+
ram/memory: RAM in GB (1, 2, 4, 8, 16, 32, or 64) - only used when creating
|
|
39
|
+
cpu: CPU cores (1, 2, 4, 8, or 16) - only used when creating
|
|
40
|
+
os: Operating system ("linux" or "windows") - only used when creating
|
|
41
|
+
gpu: GPU type - only used when creating
|
|
37
42
|
|
|
38
|
-
|
|
43
|
+
Examples:
|
|
44
|
+
# Create computer in new project
|
|
45
|
+
computer = Computer(ram=4, cpu=2)
|
|
46
|
+
|
|
47
|
+
# Create computer in existing project
|
|
48
|
+
computer = Computer(project="manus", ram=4, cpu=2)
|
|
49
|
+
|
|
50
|
+
# Connect to existing computer in project
|
|
51
|
+
computer = Computer(project="manus")
|
|
39
52
|
"""
|
|
40
53
|
self.api_key = api_key or os.environ.get("ORGO_API_KEY")
|
|
41
54
|
self.base_api_url = base_api_url
|
|
@@ -45,241 +58,246 @@ class Computer:
|
|
|
45
58
|
if ram is None and memory is not None:
|
|
46
59
|
ram = memory
|
|
47
60
|
|
|
48
|
-
#
|
|
49
|
-
|
|
50
|
-
|
|
61
|
+
# Store configuration
|
|
62
|
+
self.os = os or "linux"
|
|
63
|
+
self.ram = ram or 2
|
|
64
|
+
self.cpu = cpu or 2
|
|
65
|
+
self.gpu = gpu or "none"
|
|
51
66
|
|
|
52
|
-
if
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
67
|
+
if computer_id:
|
|
68
|
+
# Connect to existing computer by ID
|
|
69
|
+
self._connect_by_id(computer_id)
|
|
70
|
+
elif project:
|
|
71
|
+
# Work with specified project
|
|
72
|
+
if isinstance(project, str):
|
|
73
|
+
# Project name provided
|
|
74
|
+
self.project_name = project
|
|
75
|
+
self._initialize_with_project_name(project, name)
|
|
76
|
+
else:
|
|
77
|
+
# Project instance provided
|
|
78
|
+
from .project import Project as ProjectClass
|
|
79
|
+
if isinstance(project, ProjectClass):
|
|
80
|
+
self.project_name = project.name
|
|
81
|
+
self.project_id = project.id
|
|
82
|
+
self._initialize_with_project_instance(project, name)
|
|
83
|
+
else:
|
|
84
|
+
raise ValueError("project must be a string (project name) or Project instance")
|
|
62
85
|
else:
|
|
63
|
-
|
|
86
|
+
# No project specified, create a new one
|
|
87
|
+
self._create_new_project_and_computer(name)
|
|
88
|
+
|
|
89
|
+
def _connect_by_id(self, computer_id: str):
|
|
90
|
+
"""Connect to existing computer by ID"""
|
|
91
|
+
self.computer_id = computer_id
|
|
92
|
+
self._info = self.api.get_computer(computer_id)
|
|
93
|
+
self.project_name = self._info.get("project_name")
|
|
94
|
+
self.name = self._info.get("name")
|
|
95
|
+
|
|
96
|
+
def _initialize_with_project_name(self, project_name: str, computer_name: Optional[str]):
|
|
97
|
+
"""Initialize with a project name (create project if needed)"""
|
|
98
|
+
try:
|
|
99
|
+
# Try to get existing project
|
|
100
|
+
project = self.api.get_project_by_name(project_name)
|
|
101
|
+
self.project_id = project.get("id")
|
|
102
|
+
|
|
103
|
+
# Check for existing computers
|
|
104
|
+
computers = self.api.list_computers(project_name)
|
|
64
105
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
106
|
+
if computer_name:
|
|
107
|
+
# Look for specific computer
|
|
108
|
+
existing = next((c for c in computers if c.get("name") == computer_name), None)
|
|
109
|
+
if existing:
|
|
110
|
+
self._connect_to_existing_computer(existing)
|
|
111
|
+
else:
|
|
112
|
+
# Create new computer with specified name
|
|
113
|
+
self._create_computer(project_name, computer_name)
|
|
114
|
+
elif computers:
|
|
115
|
+
# No name specified, use first available computer
|
|
116
|
+
self._connect_to_existing_computer(computers[0])
|
|
117
|
+
else:
|
|
118
|
+
# No computers exist, create new one
|
|
119
|
+
self._create_computer(project_name, computer_name)
|
|
120
|
+
|
|
121
|
+
except Exception:
|
|
122
|
+
# Project doesn't exist, create it
|
|
123
|
+
logger.info(f"Project {project_name} not found, creating new project")
|
|
124
|
+
project = self.api.create_project(project_name)
|
|
125
|
+
self.project_id = project.get("id")
|
|
126
|
+
self._create_computer(project_name, computer_name)
|
|
127
|
+
|
|
128
|
+
def _initialize_with_project_instance(self, project: 'Project', computer_name: Optional[str]):
|
|
129
|
+
"""Initialize with a Project instance"""
|
|
130
|
+
computers = project.list_computers()
|
|
74
131
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
if
|
|
78
|
-
|
|
132
|
+
if computer_name:
|
|
133
|
+
# Look for specific computer
|
|
134
|
+
existing = next((c for c in computers if c.get("name") == computer_name), None)
|
|
135
|
+
if existing:
|
|
136
|
+
self._connect_to_existing_computer(existing)
|
|
79
137
|
else:
|
|
80
|
-
#
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
#
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
138
|
+
# Create new computer with specified name
|
|
139
|
+
self._create_computer(project.name, computer_name)
|
|
140
|
+
elif computers:
|
|
141
|
+
# No name specified, use first available computer
|
|
142
|
+
self._connect_to_existing_computer(computers[0])
|
|
143
|
+
else:
|
|
144
|
+
# No computers exist, create new one
|
|
145
|
+
self._create_computer(project.name, computer_name)
|
|
146
|
+
|
|
147
|
+
def _create_new_project_and_computer(self, computer_name: Optional[str]):
|
|
148
|
+
"""Create a new project and computer"""
|
|
149
|
+
# Generate a unique project name
|
|
150
|
+
project_name = f"project-{uuid.uuid4().hex[:8]}"
|
|
88
151
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
self.
|
|
152
|
+
# Create the project
|
|
153
|
+
project = self.api.create_project(project_name)
|
|
154
|
+
self.project_id = project.get("id")
|
|
155
|
+
self.project_name = project_name
|
|
92
156
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
157
|
+
# Create a computer in the new project
|
|
158
|
+
self._create_computer(project_name, computer_name)
|
|
159
|
+
|
|
160
|
+
def _connect_to_existing_computer(self, computer_info: Dict[str, Any]):
|
|
161
|
+
"""Connect to an existing computer"""
|
|
162
|
+
self.computer_id = computer_info.get("id")
|
|
163
|
+
self.name = computer_info.get("name")
|
|
164
|
+
self._info = computer_info
|
|
165
|
+
logger.info(f"Connected to existing computer {self.name} (ID: {self.computer_id})")
|
|
166
|
+
|
|
167
|
+
def _create_computer(self, project_name: str, computer_name: Optional[str]):
|
|
168
|
+
"""Create a new computer in the project"""
|
|
169
|
+
# Generate name if not provided
|
|
170
|
+
if not computer_name:
|
|
171
|
+
computer_name = f"desktop-{uuid.uuid4().hex[:8]}"
|
|
172
|
+
|
|
173
|
+
self.name = computer_name
|
|
174
|
+
|
|
175
|
+
# Validate parameters
|
|
176
|
+
if self.ram not in [1, 2, 4, 8, 16, 32, 64]:
|
|
177
|
+
raise ValueError("ram must be one of: 1, 2, 4, 8, 16, 32, 64 GB")
|
|
178
|
+
if self.cpu not in [1, 2, 4, 8, 16]:
|
|
179
|
+
raise ValueError("cpu must be one of: 1, 2, 4, 8, 16 cores")
|
|
180
|
+
if self.os not in ["linux", "windows"]:
|
|
181
|
+
raise ValueError("os must be either 'linux' or 'windows'")
|
|
182
|
+
if self.gpu not in ["none", "a10", "l40s", "a100-40gb", "a100-80gb"]:
|
|
183
|
+
raise ValueError("gpu must be one of: 'none', 'a10', 'l40s', 'a100-40gb', 'a100-80gb'")
|
|
184
|
+
|
|
185
|
+
computer = self.api.create_computer(
|
|
186
|
+
project_name=project_name,
|
|
187
|
+
computer_name=computer_name,
|
|
188
|
+
os=self.os,
|
|
189
|
+
ram=self.ram,
|
|
190
|
+
cpu=self.cpu,
|
|
191
|
+
gpu=self.gpu
|
|
192
|
+
)
|
|
193
|
+
self.computer_id = computer.get("id")
|
|
194
|
+
self._info = computer
|
|
195
|
+
logger.info(f"Created new computer {self.name} (ID: {self.computer_id})")
|
|
98
196
|
|
|
99
197
|
def status(self) -> Dict[str, Any]:
|
|
100
198
|
"""Get current computer status"""
|
|
101
|
-
return self.api.
|
|
199
|
+
return self.api.get_computer(self.computer_id)
|
|
102
200
|
|
|
103
201
|
def start(self) -> Dict[str, Any]:
|
|
104
202
|
"""Start the computer"""
|
|
105
|
-
return self.api.start_computer(self.
|
|
203
|
+
return self.api.start_computer(self.computer_id)
|
|
106
204
|
|
|
107
205
|
def stop(self) -> Dict[str, Any]:
|
|
108
206
|
"""Stop the computer"""
|
|
109
|
-
return self.api.stop_computer(self.
|
|
207
|
+
return self.api.stop_computer(self.computer_id)
|
|
110
208
|
|
|
111
209
|
def restart(self) -> Dict[str, Any]:
|
|
112
210
|
"""Restart the computer"""
|
|
113
|
-
return self.api.restart_computer(self.
|
|
211
|
+
return self.api.restart_computer(self.computer_id)
|
|
114
212
|
|
|
115
213
|
def destroy(self) -> Dict[str, Any]:
|
|
116
214
|
"""Terminate and delete the computer instance"""
|
|
117
|
-
|
|
118
|
-
# Clear the local project cache after destroying
|
|
119
|
-
ProjectManager.clear_project_cache()
|
|
120
|
-
return result
|
|
215
|
+
return self.api.delete_computer(self.computer_id)
|
|
121
216
|
|
|
122
217
|
# Navigation methods
|
|
123
218
|
def left_click(self, x: int, y: int) -> Dict[str, Any]:
|
|
124
219
|
"""Perform left mouse click at specified coordinates"""
|
|
125
|
-
return self.api.left_click(self.
|
|
220
|
+
return self.api.left_click(self.computer_id, x, y)
|
|
126
221
|
|
|
127
222
|
def right_click(self, x: int, y: int) -> Dict[str, Any]:
|
|
128
223
|
"""Perform right mouse click at specified coordinates"""
|
|
129
|
-
return self.api.right_click(self.
|
|
224
|
+
return self.api.right_click(self.computer_id, x, y)
|
|
130
225
|
|
|
131
226
|
def double_click(self, x: int, y: int) -> Dict[str, Any]:
|
|
132
227
|
"""Perform double click at specified coordinates"""
|
|
133
|
-
return self.api.double_click(self.
|
|
228
|
+
return self.api.double_click(self.computer_id, x, y)
|
|
134
229
|
|
|
135
230
|
def drag(self, start_x: int, start_y: int, end_x: int, end_y: int,
|
|
136
231
|
button: str = "left", duration: float = 0.5) -> Dict[str, Any]:
|
|
137
|
-
"""
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
Args:
|
|
141
|
-
start_x: Starting X coordinate
|
|
142
|
-
start_y: Starting Y coordinate
|
|
143
|
-
end_x: Ending X coordinate
|
|
144
|
-
end_y: Ending Y coordinate
|
|
145
|
-
button: Mouse button to use ("left" or "right", default: "left")
|
|
146
|
-
duration: Duration of the drag in seconds (0.1 to 5.0, default: 0.5)
|
|
147
|
-
|
|
148
|
-
Returns:
|
|
149
|
-
Dict with operation result
|
|
150
|
-
"""
|
|
151
|
-
return self.api.drag(self.project_id, start_x, start_y, end_x, end_y, button, duration)
|
|
232
|
+
"""Perform a smooth drag operation from start to end coordinates"""
|
|
233
|
+
return self.api.drag(self.computer_id, start_x, start_y, end_x, end_y, button, duration)
|
|
152
234
|
|
|
153
|
-
def scroll(self, direction: str = "down", amount: int =
|
|
235
|
+
def scroll(self, direction: str = "down", amount: int = 3) -> Dict[str, Any]:
|
|
154
236
|
"""Scroll in specified direction and amount"""
|
|
155
|
-
return self.api.scroll(self.
|
|
237
|
+
return self.api.scroll(self.computer_id, direction, amount)
|
|
156
238
|
|
|
157
239
|
# Input methods
|
|
158
240
|
def type(self, text: str) -> Dict[str, Any]:
|
|
159
241
|
"""Type the specified text"""
|
|
160
|
-
return self.api.type_text(self.
|
|
242
|
+
return self.api.type_text(self.computer_id, text)
|
|
161
243
|
|
|
162
244
|
def key(self, key: str) -> Dict[str, Any]:
|
|
163
245
|
"""Press a key or key combination (e.g., "Enter", "ctrl+c")"""
|
|
164
|
-
return self.api.key_press(self.
|
|
246
|
+
return self.api.key_press(self.computer_id, key)
|
|
165
247
|
|
|
166
248
|
# View methods
|
|
167
249
|
def screenshot(self) -> Image.Image:
|
|
168
250
|
"""Capture screenshot and return as PIL Image"""
|
|
169
|
-
response = self.api.get_screenshot(self.
|
|
251
|
+
response = self.api.get_screenshot(self.computer_id)
|
|
170
252
|
image_data = response.get("image", "")
|
|
171
253
|
|
|
172
|
-
# Check if it's a URL (new format) or base64 (legacy format)
|
|
173
254
|
if image_data.startswith(('http://', 'https://')):
|
|
174
|
-
# Download image from URL
|
|
175
255
|
img_response = requests.get(image_data)
|
|
176
256
|
img_response.raise_for_status()
|
|
177
257
|
return Image.open(io.BytesIO(img_response.content))
|
|
178
258
|
else:
|
|
179
|
-
# Legacy base64 format
|
|
180
259
|
img_data = base64.b64decode(image_data)
|
|
181
260
|
return Image.open(io.BytesIO(img_data))
|
|
182
261
|
|
|
183
262
|
def screenshot_base64(self) -> str:
|
|
184
263
|
"""Capture screenshot and return as base64 string"""
|
|
185
|
-
response = self.api.get_screenshot(self.
|
|
264
|
+
response = self.api.get_screenshot(self.computer_id)
|
|
186
265
|
image_data = response.get("image", "")
|
|
187
266
|
|
|
188
|
-
# Check if it's a URL (new format) or base64 (legacy format)
|
|
189
267
|
if image_data.startswith(('http://', 'https://')):
|
|
190
|
-
# Download image from URL and convert to base64
|
|
191
268
|
img_response = requests.get(image_data)
|
|
192
269
|
img_response.raise_for_status()
|
|
193
270
|
return base64.b64encode(img_response.content).decode('utf-8')
|
|
194
271
|
else:
|
|
195
|
-
# Already base64
|
|
196
272
|
return image_data
|
|
197
273
|
|
|
198
274
|
# Execution methods
|
|
199
275
|
def bash(self, command: str) -> str:
|
|
200
276
|
"""Execute a bash command and return output"""
|
|
201
|
-
response = self.api.execute_bash(self.
|
|
277
|
+
response = self.api.execute_bash(self.computer_id, command)
|
|
202
278
|
return response.get("output", "")
|
|
203
279
|
|
|
204
280
|
def exec(self, code: str, timeout: int = 10) -> Dict[str, Any]:
|
|
205
|
-
"""
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
Args:
|
|
209
|
-
code: Python code to execute
|
|
210
|
-
timeout: Maximum execution time in seconds (default: 10, max: 300)
|
|
211
|
-
|
|
212
|
-
Returns:
|
|
213
|
-
Dict with keys:
|
|
214
|
-
- success: bool indicating if execution completed without errors
|
|
215
|
-
- output: str containing stdout output
|
|
216
|
-
- error: str containing error message if any
|
|
217
|
-
- error_type: str with exception type name if error occurred
|
|
218
|
-
- timeout: bool indicating if execution timed out
|
|
219
|
-
|
|
220
|
-
Example:
|
|
221
|
-
result = computer.exec('''
|
|
222
|
-
import os
|
|
223
|
-
print(f"Current directory: {os.getcwd()}")
|
|
224
|
-
print(f"Files: {os.listdir('.')}")
|
|
225
|
-
''')
|
|
226
|
-
|
|
227
|
-
if result['success']:
|
|
228
|
-
print(result['output'])
|
|
229
|
-
else:
|
|
230
|
-
print(f"Error: {result['error']}")
|
|
231
|
-
"""
|
|
232
|
-
response = self.api.execute_python(self.project_id, code, timeout)
|
|
281
|
+
"""Execute Python code on the remote computer"""
|
|
282
|
+
response = self.api.execute_python(self.computer_id, code, timeout)
|
|
233
283
|
return response
|
|
234
284
|
|
|
235
285
|
def wait(self, seconds: float) -> Dict[str, Any]:
|
|
236
286
|
"""Wait for specified number of seconds"""
|
|
237
|
-
return self.api.wait(self.
|
|
287
|
+
return self.api.wait(self.computer_id, seconds)
|
|
238
288
|
|
|
239
289
|
# Streaming methods
|
|
240
290
|
def start_stream(self, connection: str) -> Dict[str, Any]:
|
|
241
|
-
"""
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
Args:
|
|
245
|
-
connection: Name of the RTMP connection configured in settings (e.g., "my-twitch-1")
|
|
246
|
-
|
|
247
|
-
Returns:
|
|
248
|
-
Dict with streaming status information
|
|
249
|
-
|
|
250
|
-
Example:
|
|
251
|
-
# First configure a connection in settings at https://www.orgo.ai/settings
|
|
252
|
-
# Then start streaming
|
|
253
|
-
computer.start_stream("my-twitch-1")
|
|
254
|
-
|
|
255
|
-
# Do your demo/automation
|
|
256
|
-
computer.type("Hello viewers!")
|
|
257
|
-
|
|
258
|
-
# Stop streaming when done
|
|
259
|
-
computer.stop_stream()
|
|
260
|
-
"""
|
|
261
|
-
return self.api.start_stream(self.project_id, connection)
|
|
291
|
+
"""Start streaming the computer screen to an RTMP server"""
|
|
292
|
+
return self.api.start_stream(self.computer_id, connection)
|
|
262
293
|
|
|
263
294
|
def stop_stream(self) -> Dict[str, Any]:
|
|
264
|
-
"""
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
Returns:
|
|
268
|
-
Dict with stop status information
|
|
269
|
-
"""
|
|
270
|
-
return self.api.stop_stream(self.project_id)
|
|
295
|
+
"""Stop the active stream"""
|
|
296
|
+
return self.api.stop_stream(self.computer_id)
|
|
271
297
|
|
|
272
298
|
def stream_status(self) -> Dict[str, Any]:
|
|
273
|
-
"""
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
Returns:
|
|
277
|
-
Dict with keys:
|
|
278
|
-
- status: "idle", "streaming", or "terminated"
|
|
279
|
-
- start_time: ISO timestamp when stream started (if streaming)
|
|
280
|
-
- pid: Process ID of ffmpeg (if streaming)
|
|
281
|
-
"""
|
|
282
|
-
return self.api.get_stream_status(self.project_id)
|
|
299
|
+
"""Get the current streaming status"""
|
|
300
|
+
return self.api.get_stream_status(self.computer_id)
|
|
283
301
|
|
|
284
302
|
# AI control method
|
|
285
303
|
def prompt(self,
|
|
@@ -295,32 +313,11 @@ print(f"Files: {os.listdir('.')}")
|
|
|
295
313
|
max_iterations: int = 20,
|
|
296
314
|
max_saved_screenshots: int = 5,
|
|
297
315
|
api_key: Optional[str] = None) -> List[Dict[str, Any]]:
|
|
298
|
-
"""
|
|
299
|
-
Control the computer with natural language instructions using an AI assistant.
|
|
300
|
-
|
|
301
|
-
Args:
|
|
302
|
-
instruction: What you want the AI to do with the computer
|
|
303
|
-
provider: AI provider to use (default: "anthropic")
|
|
304
|
-
model: Model to use (default: "claude-3-7-sonnet-20250219")
|
|
305
|
-
display_width: Screen width in pixels
|
|
306
|
-
display_height: Screen height in pixels
|
|
307
|
-
callback: Optional callback function for progress updates
|
|
308
|
-
thinking_enabled: Enable Claude's thinking capability (default: False)
|
|
309
|
-
thinking_budget: Token budget for thinking (default: 1024)
|
|
310
|
-
max_tokens: Maximum tokens for model response
|
|
311
|
-
max_iterations: Maximum number of agent loop iterations
|
|
312
|
-
max_saved_screenshots: Maximum number of screenshots to keep in history (default: 5)
|
|
313
|
-
api_key: API key for the AI provider (defaults to env var)
|
|
314
|
-
|
|
315
|
-
Returns:
|
|
316
|
-
List of messages from the conversation
|
|
317
|
-
"""
|
|
318
|
-
# Get the provider instance
|
|
316
|
+
"""Control the computer with natural language instructions using an AI assistant"""
|
|
319
317
|
provider_instance = get_provider(provider)
|
|
320
318
|
|
|
321
|
-
# Execute the prompt
|
|
322
319
|
return provider_instance.execute(
|
|
323
|
-
computer_id=self.
|
|
320
|
+
computer_id=self.computer_id,
|
|
324
321
|
instruction=instruction,
|
|
325
322
|
callback=callback,
|
|
326
323
|
api_key=api_key,
|
|
@@ -332,7 +329,9 @@ print(f"Files: {os.listdir('.')}")
|
|
|
332
329
|
max_tokens=max_tokens,
|
|
333
330
|
max_iterations=max_iterations,
|
|
334
331
|
max_saved_screenshots=max_saved_screenshots,
|
|
335
|
-
# Pass through the Orgo API client configuration
|
|
336
332
|
orgo_api_key=self.api_key,
|
|
337
333
|
orgo_base_url=self.base_api_url
|
|
338
|
-
)
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
def __repr__(self):
|
|
337
|
+
return f"Computer(name='{self.name}', project='{self.project_name}', id='{self.computer_id}')"
|
orgo/project.py
CHANGED
|
@@ -1,64 +1,87 @@
|
|
|
1
|
-
|
|
2
|
-
"""Project management for Orgo virtual environments"""
|
|
1
|
+
"""Project class for managing Orgo projects"""
|
|
3
2
|
import os
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import logging
|
|
7
|
-
from typing import Optional
|
|
3
|
+
import uuid
|
|
4
|
+
from typing import Dict, List, Any, Optional
|
|
8
5
|
|
|
9
|
-
|
|
6
|
+
from .api.client import ApiClient
|
|
10
7
|
|
|
11
|
-
class
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
"""
|
|
17
|
-
|
|
8
|
+
class Project:
|
|
9
|
+
def __init__(self,
|
|
10
|
+
name: Optional[str] = None,
|
|
11
|
+
api_key: Optional[str] = None,
|
|
12
|
+
base_api_url: Optional[str] = None):
|
|
13
|
+
"""
|
|
14
|
+
Initialize an Orgo project.
|
|
18
15
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
config_dir = ProjectManager._get_project_dir()
|
|
34
|
-
config_path = ProjectManager._get_config_path()
|
|
16
|
+
Args:
|
|
17
|
+
name: Project name. If exists, connects to it. If not, creates it.
|
|
18
|
+
api_key: Orgo API key (defaults to ORGO_API_KEY env var)
|
|
19
|
+
base_api_url: Custom API URL (optional)
|
|
20
|
+
"""
|
|
21
|
+
self.api_key = api_key or os.environ.get("ORGO_API_KEY")
|
|
22
|
+
self.base_api_url = base_api_url
|
|
23
|
+
self.api = ApiClient(self.api_key, self.base_api_url)
|
|
24
|
+
|
|
25
|
+
if name:
|
|
26
|
+
self.name = name
|
|
27
|
+
else:
|
|
28
|
+
# Generate a unique name if not provided
|
|
29
|
+
self.name = f"project-{uuid.uuid4().hex[:8]}"
|
|
35
30
|
|
|
31
|
+
# Try to get existing project or create new one
|
|
32
|
+
self._initialize_project()
|
|
33
|
+
|
|
34
|
+
def _initialize_project(self):
|
|
35
|
+
"""Get existing project or create new one"""
|
|
36
36
|
try:
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
37
|
+
# Try to get existing project
|
|
38
|
+
project = self.api.get_project_by_name(self.name)
|
|
39
|
+
self.id = project.get("id")
|
|
40
|
+
self._info = project
|
|
41
|
+
except Exception:
|
|
42
|
+
# Project doesn't exist, create it
|
|
43
|
+
project = self.api.create_project(self.name)
|
|
44
|
+
self.id = project.get("id")
|
|
45
|
+
self._info = project
|
|
43
46
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
project_dir = ProjectManager._get_project_dir()
|
|
48
|
-
|
|
49
|
-
if os.path.exists(project_dir):
|
|
50
|
-
try:
|
|
51
|
-
shutil.rmtree(project_dir)
|
|
52
|
-
logger.info(f"Cleared project cache at {project_dir}")
|
|
53
|
-
except (IOError, OSError) as e:
|
|
54
|
-
logger.warning(f"Failed to clear project cache: {str(e)}")
|
|
47
|
+
def status(self) -> Dict[str, Any]:
|
|
48
|
+
"""Get project status"""
|
|
49
|
+
return self.api.get_project(self.id)
|
|
55
50
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
51
|
+
def start(self) -> Dict[str, Any]:
|
|
52
|
+
"""Start all computers in the project"""
|
|
53
|
+
return self.api.start_project(self.id)
|
|
54
|
+
|
|
55
|
+
def stop(self) -> Dict[str, Any]:
|
|
56
|
+
"""Stop all computers in the project"""
|
|
57
|
+
return self.api.stop_project(self.id)
|
|
58
|
+
|
|
59
|
+
def restart(self) -> Dict[str, Any]:
|
|
60
|
+
"""Restart all computers in the project"""
|
|
61
|
+
return self.api.restart_project(self.id)
|
|
62
|
+
|
|
63
|
+
def destroy(self) -> Dict[str, Any]:
|
|
64
|
+
"""Delete the project and all its computers"""
|
|
65
|
+
return self.api.delete_project(self.id)
|
|
66
|
+
|
|
67
|
+
def list_computers(self) -> List[Dict[str, Any]]:
|
|
68
|
+
"""List all computers in this project"""
|
|
69
|
+
return self.api.list_computers(self.name)
|
|
70
|
+
|
|
71
|
+
def get_computer(self, computer_name: str = None) -> Optional[Dict[str, Any]]:
|
|
72
|
+
"""Get a specific computer in this project by name, or the first one if no name specified"""
|
|
73
|
+
computers = self.list_computers()
|
|
74
|
+
if not computers:
|
|
75
|
+
return None
|
|
76
|
+
|
|
77
|
+
if computer_name:
|
|
78
|
+
for computer in computers:
|
|
79
|
+
if computer.get("name") == computer_name:
|
|
80
|
+
return computer
|
|
81
|
+
return None
|
|
82
|
+
else:
|
|
83
|
+
# Return first computer if no name specified
|
|
84
|
+
return computers[0]
|
|
60
85
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
"""Get the full path to the config file"""
|
|
64
|
-
return os.path.join(ProjectManager._get_project_dir(), "project.json")
|
|
86
|
+
def __repr__(self):
|
|
87
|
+
return f"Project(name='{self.name}', id='{self.id}')"
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
orgo/__init__.py,sha256=aw3BM7-Wy8jk-mvIWRG2gC4-nsc74s6ZFm1U21NyGeM,171
|
|
2
|
+
orgo/computer.py,sha256=w_Cvjw1pspkV14CodgNJpDvYAOvL3tS6KfDiUsEcC3s,14463
|
|
3
|
+
orgo/project.py,sha256=u6kxoUUj9xAxzVWl2A4kD4EHmQWrrwTixkIVRhH4Ehc,3072
|
|
4
|
+
orgo/prompt.py,sha256=ynblwXPTDp_aF1MbGBsY0PIEr9naklDaKFcfSE_EZ6E,19781
|
|
5
|
+
orgo/api/__init__.py,sha256=9Tzb_OPJ5DH7Cg7OrHzpZZUT4ip05alpa9RLDYmnId8,113
|
|
6
|
+
orgo/api/client.py,sha256=JofBHTnks4nO60786vAmzJmECVFFf-Yff1qF0ir-YQ8,7958
|
|
7
|
+
orgo/utils/__init__.py,sha256=W4G_nwGBf_7jy0w_mfcrkllurYHSRU4B5cMTVYH_uCc,123
|
|
8
|
+
orgo/utils/auth.py,sha256=tPLBJY-6gdBQWLUjUbwIwxHphC3KoRT_XgP3Iykw3Mw,509
|
|
9
|
+
orgo-0.0.26.dist-info/METADATA,sha256=o92pMm2FgkRbLYQzgw_LLLRgpiJvyDBjdT7M-ug1m9w,822
|
|
10
|
+
orgo-0.0.26.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
11
|
+
orgo-0.0.26.dist-info/top_level.txt,sha256=q0rYtFji8GbYuhFW8A5Ab9e0j27761IKPhnL0E9xow4,5
|
|
12
|
+
orgo-0.0.26.dist-info/RECORD,,
|
orgo-0.0.24.dist-info/RECORD
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
orgo/__init__.py,sha256=TlOzDJqRKotAam631MdZcz8ypAqyWrLo-Px8HWLkrD0,131
|
|
2
|
-
orgo/computer.py,sha256=44jqFqANITalNt-7hsDn-UKVJZCyqR-63wrxAyTnpmw,13629
|
|
3
|
-
orgo/project.py,sha256=F90mYJKRZAoFSFw8HVAiosMoLyzVlf0QaKJgTyXoJ4A,2267
|
|
4
|
-
orgo/prompt.py,sha256=ynblwXPTDp_aF1MbGBsY0PIEr9naklDaKFcfSE_EZ6E,19781
|
|
5
|
-
orgo/api/__init__.py,sha256=9Tzb_OPJ5DH7Cg7OrHzpZZUT4ip05alpa9RLDYmnId8,113
|
|
6
|
-
orgo/api/client.py,sha256=MgOg5shKzsOVsablvtTa39_ms-OdrmFMW1pD07C6PTs,7045
|
|
7
|
-
orgo/utils/__init__.py,sha256=W4G_nwGBf_7jy0w_mfcrkllurYHSRU4B5cMTVYH_uCc,123
|
|
8
|
-
orgo/utils/auth.py,sha256=tPLBJY-6gdBQWLUjUbwIwxHphC3KoRT_XgP3Iykw3Mw,509
|
|
9
|
-
orgo-0.0.24.dist-info/METADATA,sha256=uidJBILPzxo19T-6DJS6xYb8JSqSMfxuEU8113xAQMA,822
|
|
10
|
-
orgo-0.0.24.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
11
|
-
orgo-0.0.24.dist-info/top_level.txt,sha256=q0rYtFji8GbYuhFW8A5Ab9e0j27761IKPhnL0E9xow4,5
|
|
12
|
-
orgo-0.0.24.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|