orgo 0.0.39__py3-none-any.whl → 0.0.40__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 +11 -8
- orgo/api/__init__.py +5 -5
- orgo/api/client.py +227 -227
- orgo/computer.py +471 -466
- orgo/forge.py +176 -176
- orgo/project.py +86 -86
- orgo/prompt.py +1015 -1015
- orgo/utils/__init__.py +5 -5
- orgo/utils/auth.py +16 -16
- {orgo-0.0.39.dist-info → orgo-0.0.40.dist-info}/METADATA +47 -47
- orgo-0.0.40.dist-info/RECORD +13 -0
- {orgo-0.0.39.dist-info → orgo-0.0.40.dist-info}/WHEEL +1 -1
- orgo-0.0.39.dist-info/RECORD +0 -13
- {orgo-0.0.39.dist-info → orgo-0.0.40.dist-info}/top_level.txt +0 -0
orgo/computer.py
CHANGED
|
@@ -1,467 +1,472 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Orgo Computer - Control virtual computers with AI.
|
|
3
|
-
|
|
4
|
-
Usage:
|
|
5
|
-
from orgo import Computer
|
|
6
|
-
|
|
7
|
-
computer = Computer(project="your-project")
|
|
8
|
-
computer.prompt("Open Firefox and search for AI news")
|
|
9
|
-
"""
|
|
10
|
-
|
|
11
|
-
import os as operating_system
|
|
12
|
-
import base64
|
|
13
|
-
import logging
|
|
14
|
-
import uuid
|
|
15
|
-
import io
|
|
16
|
-
import random
|
|
17
|
-
from typing import Dict, List, Any, Optional, Callable, Literal, Union
|
|
18
|
-
from PIL import Image
|
|
19
|
-
import requests
|
|
20
|
-
from requests.exceptions import RequestException
|
|
21
|
-
|
|
22
|
-
from .api.client import ApiClient
|
|
23
|
-
from .prompt import get_provider
|
|
24
|
-
|
|
25
|
-
logger = logging.getLogger(__name__)
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
def _generate_computer_name() -> str:
|
|
29
|
-
"""Generate a random computer name like 'computer-1568'"""
|
|
30
|
-
return f"computer-{random.randint(1000, 9999)}"
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
def _print_success(message: str):
|
|
34
|
-
"""Print a success message with nice formatting"""
|
|
35
|
-
print(f"✓ {message}")
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
def _print_error(message: str):
|
|
39
|
-
"""Print an error message with nice formatting"""
|
|
40
|
-
print(f"✗ {message}")
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def _print_info(message: str):
|
|
44
|
-
"""Print an info message with nice formatting"""
|
|
45
|
-
print(f"→ {message}")
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
class Computer:
|
|
49
|
-
"""
|
|
50
|
-
Control an Orgo virtual computer.
|
|
51
|
-
|
|
52
|
-
Examples:
|
|
53
|
-
# Create computer in new/existing project
|
|
54
|
-
computer = Computer(project="my-project")
|
|
55
|
-
|
|
56
|
-
# Create with specific name
|
|
57
|
-
computer = Computer(project="my-project", name="dev-machine")
|
|
58
|
-
|
|
59
|
-
# Connect to existing computer by ID
|
|
60
|
-
computer = Computer(computer_id="abc123")
|
|
61
|
-
|
|
62
|
-
# AI control (uses Orgo by default)
|
|
63
|
-
computer.prompt("Open Firefox")
|
|
64
|
-
|
|
65
|
-
# AI control with Anthropic directly
|
|
66
|
-
computer.prompt("Open Firefox", provider="anthropic")
|
|
67
|
-
"""
|
|
68
|
-
|
|
69
|
-
def __init__(self,
|
|
70
|
-
project: Optional[Union[str, 'Project']] = None,
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
self.
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
if
|
|
118
|
-
self.
|
|
119
|
-
|
|
120
|
-
self.
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
self.
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
self.
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
self.
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
if
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
if
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
"""
|
|
302
|
-
return self.api.
|
|
303
|
-
|
|
304
|
-
def
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
def
|
|
357
|
-
"""Execute
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
1
|
+
"""
|
|
2
|
+
Orgo Computer - Control virtual computers with AI.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
from orgo import Computer
|
|
6
|
+
|
|
7
|
+
computer = Computer(project="your-project")
|
|
8
|
+
computer.prompt("Open Firefox and search for AI news")
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import os as operating_system
|
|
12
|
+
import base64
|
|
13
|
+
import logging
|
|
14
|
+
import uuid
|
|
15
|
+
import io
|
|
16
|
+
import random
|
|
17
|
+
from typing import Dict, List, Any, Optional, Callable, Literal, Union
|
|
18
|
+
from PIL import Image
|
|
19
|
+
import requests
|
|
20
|
+
from requests.exceptions import RequestException
|
|
21
|
+
|
|
22
|
+
from .api.client import ApiClient
|
|
23
|
+
from .prompt import get_provider
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _generate_computer_name() -> str:
|
|
29
|
+
"""Generate a random computer name like 'computer-1568'"""
|
|
30
|
+
return f"computer-{random.randint(1000, 9999)}"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _print_success(message: str):
|
|
34
|
+
"""Print a success message with nice formatting"""
|
|
35
|
+
print(f"✓ {message}")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _print_error(message: str):
|
|
39
|
+
"""Print an error message with nice formatting"""
|
|
40
|
+
print(f"✗ {message}")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _print_info(message: str):
|
|
44
|
+
"""Print an info message with nice formatting"""
|
|
45
|
+
print(f"→ {message}")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class Computer:
|
|
49
|
+
"""
|
|
50
|
+
Control an Orgo virtual computer.
|
|
51
|
+
|
|
52
|
+
Examples:
|
|
53
|
+
# Create computer in new/existing project
|
|
54
|
+
computer = Computer(project="my-project")
|
|
55
|
+
|
|
56
|
+
# Create with specific name
|
|
57
|
+
computer = Computer(project="my-project", name="dev-machine")
|
|
58
|
+
|
|
59
|
+
# Connect to existing computer by ID
|
|
60
|
+
computer = Computer(computer_id="abc123")
|
|
61
|
+
|
|
62
|
+
# AI control (uses Orgo by default)
|
|
63
|
+
computer.prompt("Open Firefox")
|
|
64
|
+
|
|
65
|
+
# AI control with Anthropic directly
|
|
66
|
+
computer.prompt("Open Firefox", provider="anthropic")
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
def __init__(self,
|
|
70
|
+
project: Optional[Union[str, 'Project']] = None,
|
|
71
|
+
workspace: Optional[Union[str, 'Project']] = None, # Alias for project
|
|
72
|
+
name: Optional[str] = None,
|
|
73
|
+
computer_id: Optional[str] = None,
|
|
74
|
+
api_key: Optional[str] = None,
|
|
75
|
+
base_api_url: Optional[str] = None,
|
|
76
|
+
ram: Optional[Literal[1, 2, 4, 8, 16, 32, 64]] = None,
|
|
77
|
+
memory: Optional[Literal[1, 2, 4, 8, 16, 32, 64]] = None,
|
|
78
|
+
cpu: Optional[Literal[1, 2, 4, 8, 16]] = None,
|
|
79
|
+
os: Optional[Literal["linux", "windows"]] = None,
|
|
80
|
+
gpu: Optional[Literal["none", "a10", "l40s", "a100-40gb", "a100-80gb"]] = None,
|
|
81
|
+
image: Optional[Union[str, Any]] = None,
|
|
82
|
+
verbose: bool = True):
|
|
83
|
+
"""
|
|
84
|
+
Initialize an Orgo virtual computer.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
project: Project/workspace name or instance (creates if doesn't exist)
|
|
88
|
+
workspace: Alias for project (preferred name going forward)
|
|
89
|
+
name: Computer name (auto-generated if not provided)
|
|
90
|
+
computer_id: Connect to existing computer by ID
|
|
91
|
+
api_key: Orgo API key (defaults to ORGO_API_KEY env var)
|
|
92
|
+
base_api_url: Custom API URL
|
|
93
|
+
ram/memory: RAM in GB (1, 2, 4, 8, 16, 32, 64)
|
|
94
|
+
cpu: CPU cores (1, 2, 4, 8, 16)
|
|
95
|
+
os: "linux" or "windows"
|
|
96
|
+
gpu: "none", "a10", "l40s", "a100-40gb", "a100-80gb"
|
|
97
|
+
image: Custom image reference or Forge object
|
|
98
|
+
verbose: Show console output (default: True)
|
|
99
|
+
"""
|
|
100
|
+
# workspace is an alias for project
|
|
101
|
+
if workspace is not None and project is None:
|
|
102
|
+
project = workspace
|
|
103
|
+
self.api_key = api_key or operating_system.environ.get("ORGO_API_KEY")
|
|
104
|
+
self.base_api_url = base_api_url
|
|
105
|
+
self.api = ApiClient(self.api_key, self.base_api_url)
|
|
106
|
+
self.verbose = verbose
|
|
107
|
+
|
|
108
|
+
if ram is None and memory is not None:
|
|
109
|
+
ram = memory
|
|
110
|
+
|
|
111
|
+
self.os = os or "linux"
|
|
112
|
+
self.ram = ram or 2
|
|
113
|
+
self.cpu = cpu or 2
|
|
114
|
+
self.gpu = gpu or "none"
|
|
115
|
+
self.image = image
|
|
116
|
+
|
|
117
|
+
if hasattr(self.image, 'build') and callable(self.image.build):
|
|
118
|
+
if self.verbose:
|
|
119
|
+
_print_info("Building image from Forge object...")
|
|
120
|
+
self.image = self.image.build()
|
|
121
|
+
|
|
122
|
+
if computer_id:
|
|
123
|
+
self.computer_id = computer_id
|
|
124
|
+
self.name = name
|
|
125
|
+
self.project_id = None
|
|
126
|
+
self.project_name = None
|
|
127
|
+
if self.verbose:
|
|
128
|
+
_print_success(f"Connected to computer: {self.computer_id}")
|
|
129
|
+
elif project:
|
|
130
|
+
if isinstance(project, str):
|
|
131
|
+
self.project_name = project
|
|
132
|
+
self._initialize_with_project_name(project, name)
|
|
133
|
+
else:
|
|
134
|
+
from .project import Project as ProjectClass
|
|
135
|
+
if isinstance(project, ProjectClass):
|
|
136
|
+
self.project_name = project.name
|
|
137
|
+
self.project_id = project.id
|
|
138
|
+
self._initialize_with_project_instance(project, name)
|
|
139
|
+
else:
|
|
140
|
+
raise ValueError("project must be a string or Project instance")
|
|
141
|
+
else:
|
|
142
|
+
self._create_new_project_and_computer(name)
|
|
143
|
+
|
|
144
|
+
# =========================================================================
|
|
145
|
+
# Initialization Helpers
|
|
146
|
+
# =========================================================================
|
|
147
|
+
|
|
148
|
+
def _initialize_with_project_name(self, project_name: str, computer_name: Optional[str]):
|
|
149
|
+
"""Initialize computer with project name (create project if needed)"""
|
|
150
|
+
try:
|
|
151
|
+
# Try to get existing project
|
|
152
|
+
project = self.api.get_project_by_name(project_name)
|
|
153
|
+
self.project_id = project.get("id")
|
|
154
|
+
|
|
155
|
+
# If no computer name specified, generate one
|
|
156
|
+
if not computer_name:
|
|
157
|
+
computer_name = _generate_computer_name()
|
|
158
|
+
|
|
159
|
+
# Create the computer in this project
|
|
160
|
+
self._create_computer(self.project_id, computer_name, project_name)
|
|
161
|
+
|
|
162
|
+
except Exception:
|
|
163
|
+
# Project doesn't exist, create it
|
|
164
|
+
if self.verbose:
|
|
165
|
+
_print_info(f"Creating project: {project_name}")
|
|
166
|
+
project = self.api.create_project(project_name)
|
|
167
|
+
self.project_id = project.get("id")
|
|
168
|
+
|
|
169
|
+
# Generate name if not specified
|
|
170
|
+
if not computer_name:
|
|
171
|
+
computer_name = _generate_computer_name()
|
|
172
|
+
|
|
173
|
+
self._create_computer(self.project_id, computer_name, project_name)
|
|
174
|
+
|
|
175
|
+
def _initialize_with_project_instance(self, project: 'Project', computer_name: Optional[str]):
|
|
176
|
+
"""Initialize computer with Project instance"""
|
|
177
|
+
# Generate name if not specified
|
|
178
|
+
if not computer_name:
|
|
179
|
+
computer_name = _generate_computer_name()
|
|
180
|
+
|
|
181
|
+
self._create_computer(project.id, computer_name, project.name)
|
|
182
|
+
|
|
183
|
+
def _create_new_project_and_computer(self, computer_name: Optional[str]):
|
|
184
|
+
"""Create a new project and computer when no project specified"""
|
|
185
|
+
project_name = f"project-{uuid.uuid4().hex[:8]}"
|
|
186
|
+
|
|
187
|
+
if self.verbose:
|
|
188
|
+
_print_info(f"Creating project: {project_name}")
|
|
189
|
+
|
|
190
|
+
project = self.api.create_project(project_name)
|
|
191
|
+
self.project_id = project.get("id")
|
|
192
|
+
self.project_name = project_name
|
|
193
|
+
|
|
194
|
+
# Generate name if not specified
|
|
195
|
+
if not computer_name:
|
|
196
|
+
computer_name = _generate_computer_name()
|
|
197
|
+
|
|
198
|
+
self._create_computer(self.project_id, computer_name, project_name)
|
|
199
|
+
|
|
200
|
+
def _connect_to_existing_computer(self, computer_info: Dict[str, Any]):
|
|
201
|
+
"""Connect to an existing computer"""
|
|
202
|
+
self.computer_id = computer_info.get("id")
|
|
203
|
+
self.name = computer_info.get("name")
|
|
204
|
+
if self.verbose:
|
|
205
|
+
_print_success(f"Connected to: {self.name} ({self.computer_id})")
|
|
206
|
+
|
|
207
|
+
def _create_computer(self, project_id: str, computer_name: str, project_name: str):
|
|
208
|
+
"""Create a new computer with beautiful console output"""
|
|
209
|
+
self.name = computer_name
|
|
210
|
+
|
|
211
|
+
# Validate parameters
|
|
212
|
+
if self.ram not in [1, 2, 4, 8, 16, 32, 64]:
|
|
213
|
+
raise ValueError("ram must be: 1, 2, 4, 8, 16, 32, or 64 GB")
|
|
214
|
+
if self.cpu not in [1, 2, 4, 8, 16]:
|
|
215
|
+
raise ValueError("cpu must be: 1, 2, 4, 8, or 16 cores")
|
|
216
|
+
if self.os not in ["linux", "windows"]:
|
|
217
|
+
raise ValueError("os must be: 'linux' or 'windows'")
|
|
218
|
+
if self.gpu not in ["none", "a10", "l40s", "a100-40gb", "a100-80gb"]:
|
|
219
|
+
raise ValueError("gpu must be: 'none', 'a10', 'l40s', 'a100-40gb', or 'a100-80gb'")
|
|
220
|
+
|
|
221
|
+
# Resolve image if needed
|
|
222
|
+
image_ref = self.image
|
|
223
|
+
if image_ref and isinstance(image_ref, str) and not image_ref.startswith("registry.fly.io"):
|
|
224
|
+
try:
|
|
225
|
+
project_info = self.api.get_project(project_id)
|
|
226
|
+
org_id = project_info.get("org_id", "orgo")
|
|
227
|
+
response = self.api.get_latest_build(org_id, project_id, image_ref)
|
|
228
|
+
if response and response.get("build"):
|
|
229
|
+
resolved = response.get("build", {}).get("imageRef")
|
|
230
|
+
if resolved:
|
|
231
|
+
image_ref = resolved
|
|
232
|
+
except Exception as e:
|
|
233
|
+
if self.verbose:
|
|
234
|
+
logger.warning(f"Failed to resolve image: {e}")
|
|
235
|
+
|
|
236
|
+
# Create the computer
|
|
237
|
+
try:
|
|
238
|
+
computer = self.api.create_computer(
|
|
239
|
+
project_id=project_id,
|
|
240
|
+
computer_name=computer_name,
|
|
241
|
+
os=self.os,
|
|
242
|
+
ram=self.ram,
|
|
243
|
+
cpu=self.cpu,
|
|
244
|
+
gpu=self.gpu,
|
|
245
|
+
image=image_ref
|
|
246
|
+
)
|
|
247
|
+
self.computer_id = computer.get("id")
|
|
248
|
+
|
|
249
|
+
# Beautiful success message
|
|
250
|
+
if self.verbose:
|
|
251
|
+
_print_success(
|
|
252
|
+
f"Computer [{self.name}] successfully created under workspace [{project_name}]"
|
|
253
|
+
)
|
|
254
|
+
_print_info(f"ID: {self.computer_id}")
|
|
255
|
+
_print_info(f"View at: https://orgo.ai/workspaces/{self.computer_id}")
|
|
256
|
+
|
|
257
|
+
except Exception as e:
|
|
258
|
+
if self.verbose:
|
|
259
|
+
_print_error(f"Failed to create computer: {str(e)}")
|
|
260
|
+
raise
|
|
261
|
+
|
|
262
|
+
# =========================================================================
|
|
263
|
+
# Computer Management
|
|
264
|
+
# =========================================================================
|
|
265
|
+
|
|
266
|
+
def status(self) -> Dict[str, Any]:
|
|
267
|
+
"""Get current computer status."""
|
|
268
|
+
return self.api.get_computer(self.computer_id)
|
|
269
|
+
|
|
270
|
+
def restart(self) -> Dict[str, Any]:
|
|
271
|
+
"""Restart the computer."""
|
|
272
|
+
if self.verbose:
|
|
273
|
+
_print_info(f"Restarting computer: {self.name}")
|
|
274
|
+
result = self.api.restart_computer(self.computer_id)
|
|
275
|
+
if self.verbose:
|
|
276
|
+
_print_success("Computer restarted")
|
|
277
|
+
return result
|
|
278
|
+
|
|
279
|
+
def destroy(self) -> Dict[str, Any]:
|
|
280
|
+
"""Delete the computer."""
|
|
281
|
+
if self.verbose:
|
|
282
|
+
_print_info(f"Deleting computer: {self.name}")
|
|
283
|
+
result = self.api.delete_computer(self.computer_id)
|
|
284
|
+
if self.verbose:
|
|
285
|
+
_print_success("Computer deleted")
|
|
286
|
+
return result
|
|
287
|
+
|
|
288
|
+
# =========================================================================
|
|
289
|
+
# Mouse Actions
|
|
290
|
+
# =========================================================================
|
|
291
|
+
|
|
292
|
+
def left_click(self, x: int, y: int) -> Dict[str, Any]:
|
|
293
|
+
"""Left click at coordinates."""
|
|
294
|
+
return self.api.left_click(self.computer_id, x, y)
|
|
295
|
+
|
|
296
|
+
def right_click(self, x: int, y: int) -> Dict[str, Any]:
|
|
297
|
+
"""Right click at coordinates."""
|
|
298
|
+
return self.api.right_click(self.computer_id, x, y)
|
|
299
|
+
|
|
300
|
+
def double_click(self, x: int, y: int) -> Dict[str, Any]:
|
|
301
|
+
"""Double click at coordinates."""
|
|
302
|
+
return self.api.double_click(self.computer_id, x, y)
|
|
303
|
+
|
|
304
|
+
def drag(self, start_x: int, start_y: int, end_x: int, end_y: int,
|
|
305
|
+
button: str = "left", duration: float = 0.5) -> Dict[str, Any]:
|
|
306
|
+
"""Drag from start to end coordinates."""
|
|
307
|
+
return self.api.drag(self.computer_id, start_x, start_y, end_x, end_y, button, duration)
|
|
308
|
+
|
|
309
|
+
def scroll(self, direction: str = "down", amount: int = 3) -> Dict[str, Any]:
|
|
310
|
+
"""Scroll in direction."""
|
|
311
|
+
return self.api.scroll(self.computer_id, direction, amount)
|
|
312
|
+
|
|
313
|
+
# =========================================================================
|
|
314
|
+
# Keyboard Actions
|
|
315
|
+
# =========================================================================
|
|
316
|
+
|
|
317
|
+
def type(self, text: str) -> Dict[str, Any]:
|
|
318
|
+
"""Type text."""
|
|
319
|
+
return self.api.type_text(self.computer_id, text)
|
|
320
|
+
|
|
321
|
+
def key(self, key: str) -> Dict[str, Any]:
|
|
322
|
+
"""Press key (e.g., "Enter", "ctrl+c")."""
|
|
323
|
+
return self.api.key_press(self.computer_id, key)
|
|
324
|
+
|
|
325
|
+
# =========================================================================
|
|
326
|
+
# Screen Capture
|
|
327
|
+
# =========================================================================
|
|
328
|
+
|
|
329
|
+
def screenshot(self) -> Image.Image:
|
|
330
|
+
"""Capture screenshot as PIL Image."""
|
|
331
|
+
response = self.api.get_screenshot(self.computer_id)
|
|
332
|
+
image_data = response.get("image", "")
|
|
333
|
+
|
|
334
|
+
if image_data.startswith(('http://', 'https://')):
|
|
335
|
+
img_response = requests.get(image_data)
|
|
336
|
+
img_response.raise_for_status()
|
|
337
|
+
return Image.open(io.BytesIO(img_response.content))
|
|
338
|
+
else:
|
|
339
|
+
return Image.open(io.BytesIO(base64.b64decode(image_data)))
|
|
340
|
+
|
|
341
|
+
def screenshot_base64(self) -> str:
|
|
342
|
+
"""Capture screenshot as base64 string."""
|
|
343
|
+
response = self.api.get_screenshot(self.computer_id)
|
|
344
|
+
image_data = response.get("image", "")
|
|
345
|
+
|
|
346
|
+
if image_data.startswith(('http://', 'https://')):
|
|
347
|
+
img_response = requests.get(image_data)
|
|
348
|
+
img_response.raise_for_status()
|
|
349
|
+
return base64.b64encode(img_response.content).decode('utf-8')
|
|
350
|
+
return image_data
|
|
351
|
+
|
|
352
|
+
# =========================================================================
|
|
353
|
+
# Code Execution
|
|
354
|
+
# =========================================================================
|
|
355
|
+
|
|
356
|
+
def bash(self, command: str) -> str:
|
|
357
|
+
"""Execute bash command."""
|
|
358
|
+
response = self.api.execute_bash(self.computer_id, command)
|
|
359
|
+
return response.get("output", "")
|
|
360
|
+
|
|
361
|
+
def exec(self, code: str, timeout: int = 10) -> Dict[str, Any]:
|
|
362
|
+
"""Execute Python code."""
|
|
363
|
+
return self.api.execute_python(self.computer_id, code, timeout)
|
|
364
|
+
|
|
365
|
+
def wait(self, seconds: float) -> Dict[str, Any]:
|
|
366
|
+
"""Wait for seconds."""
|
|
367
|
+
return self.api.wait(self.computer_id, seconds)
|
|
368
|
+
|
|
369
|
+
# =========================================================================
|
|
370
|
+
# Streaming
|
|
371
|
+
# =========================================================================
|
|
372
|
+
|
|
373
|
+
def start_stream(self, connection: str) -> Dict[str, Any]:
|
|
374
|
+
"""Start RTMP stream."""
|
|
375
|
+
return self.api.start_stream(self.computer_id, connection)
|
|
376
|
+
|
|
377
|
+
def stop_stream(self) -> Dict[str, Any]:
|
|
378
|
+
"""Stop stream."""
|
|
379
|
+
return self.api.stop_stream(self.computer_id)
|
|
380
|
+
|
|
381
|
+
def stream_status(self) -> Dict[str, Any]:
|
|
382
|
+
"""Get stream status."""
|
|
383
|
+
return self.api.get_stream_status(self.computer_id)
|
|
384
|
+
|
|
385
|
+
# =========================================================================
|
|
386
|
+
# AI Control
|
|
387
|
+
# =========================================================================
|
|
388
|
+
|
|
389
|
+
def prompt(self,
|
|
390
|
+
instruction: str,
|
|
391
|
+
provider: Optional[str] = None,
|
|
392
|
+
verbose: bool = True,
|
|
393
|
+
callback: Optional[Callable[[str, Any], None]] = None,
|
|
394
|
+
model: str = "claude-sonnet-4-5-20250929",
|
|
395
|
+
display_width: int = 1024,
|
|
396
|
+
display_height: int = 768,
|
|
397
|
+
thinking_enabled: bool = True,
|
|
398
|
+
thinking_budget: int = 1024,
|
|
399
|
+
max_tokens: int = 4096,
|
|
400
|
+
max_iterations: int = 100,
|
|
401
|
+
max_saved_screenshots: int = 3,
|
|
402
|
+
system_prompt: Optional[str] = None,
|
|
403
|
+
api_key: Optional[str] = None) -> List[Dict[str, Any]]:
|
|
404
|
+
"""
|
|
405
|
+
Control the computer with natural language.
|
|
406
|
+
|
|
407
|
+
Args:
|
|
408
|
+
instruction: What you want the computer to do
|
|
409
|
+
provider: "orgo" (default) or "anthropic"
|
|
410
|
+
verbose: Show progress logs (default: True)
|
|
411
|
+
callback: Optional callback for events
|
|
412
|
+
model: AI model to use
|
|
413
|
+
display_width: Screen width
|
|
414
|
+
display_height: Screen height
|
|
415
|
+
thinking_enabled: Enable extended thinking
|
|
416
|
+
thinking_budget: Token budget for thinking
|
|
417
|
+
max_tokens: Max response tokens
|
|
418
|
+
max_iterations: Max agent iterations
|
|
419
|
+
max_saved_screenshots: Screenshots to keep in context
|
|
420
|
+
system_prompt: Custom instructions
|
|
421
|
+
api_key: Anthropic key (only for provider="anthropic")
|
|
422
|
+
|
|
423
|
+
Returns:
|
|
424
|
+
List of conversation messages
|
|
425
|
+
|
|
426
|
+
Examples:
|
|
427
|
+
# Default: Uses Orgo hosted agent
|
|
428
|
+
computer.prompt("Open Firefox and search for AI news")
|
|
429
|
+
|
|
430
|
+
# Quiet mode (no logs)
|
|
431
|
+
computer.prompt("Open Firefox", verbose=False)
|
|
432
|
+
|
|
433
|
+
# Use Anthropic directly
|
|
434
|
+
computer.prompt("Open Firefox", provider="anthropic")
|
|
435
|
+
|
|
436
|
+
# With callback
|
|
437
|
+
computer.prompt("Search Google", callback=lambda t, d: print(f"{t}: {d}"))
|
|
438
|
+
"""
|
|
439
|
+
provider_instance = get_provider(provider)
|
|
440
|
+
|
|
441
|
+
return provider_instance.execute(
|
|
442
|
+
computer_id=self.computer_id,
|
|
443
|
+
instruction=instruction,
|
|
444
|
+
callback=callback,
|
|
445
|
+
verbose=verbose,
|
|
446
|
+
api_key=api_key,
|
|
447
|
+
model=model,
|
|
448
|
+
display_width=display_width,
|
|
449
|
+
display_height=display_height,
|
|
450
|
+
thinking_enabled=thinking_enabled,
|
|
451
|
+
thinking_budget=thinking_budget,
|
|
452
|
+
max_tokens=max_tokens,
|
|
453
|
+
max_iterations=max_iterations,
|
|
454
|
+
max_saved_screenshots=max_saved_screenshots,
|
|
455
|
+
system_prompt=system_prompt,
|
|
456
|
+
orgo_api_key=self.api_key,
|
|
457
|
+
orgo_base_url=self.base_api_url
|
|
458
|
+
)
|
|
459
|
+
|
|
460
|
+
# =========================================================================
|
|
461
|
+
# URL Helper
|
|
462
|
+
# =========================================================================
|
|
463
|
+
|
|
464
|
+
@property
|
|
465
|
+
def url(self) -> str:
|
|
466
|
+
"""Get the URL to view this computer."""
|
|
467
|
+
return f"https://orgo.ai/workspaces/{self.computer_id}"
|
|
468
|
+
|
|
469
|
+
def __repr__(self):
|
|
470
|
+
if hasattr(self, 'name') and self.name:
|
|
471
|
+
return f"Computer(name='{self.name}', id='{self.computer_id}')"
|
|
467
472
|
return f"Computer(id='{self.computer_id}')"
|