orgo 0.0.40__py3-none-any.whl → 0.0.41__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/template.py ADDED
@@ -0,0 +1,347 @@
1
+ """
2
+ Orgo Template - Pre-configure computer environments.
3
+
4
+ Usage:
5
+ from orgo import Template, Computer
6
+
7
+ # Create a template with pre-installed software
8
+ template = Template("python-ml")
9
+ template.run("pip install numpy pandas scikit-learn")
10
+ template.build()
11
+
12
+ # Launch computers from the template
13
+ computer = Computer(template="python-ml")
14
+ """
15
+
16
+ import os
17
+ import subprocess
18
+ import logging
19
+ from typing import Optional, List
20
+
21
+ from .api.client import ApiClient
22
+
23
+ logger = logging.getLogger(__name__)
24
+
25
+
26
+ def _print_info(message: str):
27
+ print(f"-> {message}")
28
+
29
+
30
+ def _print_success(message: str):
31
+ print(f"[ok] {message}")
32
+
33
+
34
+ def _print_error(message: str):
35
+ print(f"[error] {message}")
36
+
37
+
38
+ class Template:
39
+ """
40
+ Create reusable computer templates with pre-installed software.
41
+
42
+ Templates let you define a base environment once and launch multiple
43
+ computers from it. This is useful for:
44
+ - Pre-installing dependencies (Python packages, apt packages, etc.)
45
+ - Cloning repositories
46
+ - Setting up configuration files
47
+
48
+ Examples:
49
+ # Create and build a template
50
+ template = Template("data-science")
51
+ template.run("apt-get update && apt-get install -y python3-pip")
52
+ template.run("pip install numpy pandas matplotlib jupyter")
53
+ template.build()
54
+
55
+ # Launch a computer from the template
56
+ computer = Computer(template="data-science")
57
+
58
+ # Or pass the template object directly
59
+ computer = Computer(template=template)
60
+ """
61
+
62
+ def __init__(self,
63
+ name: str,
64
+ workspace: Optional[str] = None,
65
+ api_key: Optional[str] = None,
66
+ base_api_url: Optional[str] = None,
67
+ verbose: bool = True):
68
+ """
69
+ Initialize a Template.
70
+
71
+ Args:
72
+ name: Template name (used to reference it later)
73
+ workspace: Workspace to store the template in (auto-created if needed)
74
+ api_key: Orgo API key (defaults to ORGO_API_KEY env var)
75
+ base_api_url: Custom API URL
76
+ verbose: Show console output (default: True)
77
+ """
78
+ self.name = name
79
+ self.verbose = verbose
80
+ self.api_key = api_key or os.environ.get("ORGO_API_KEY")
81
+ self.base_api_url = base_api_url
82
+ self.api = ApiClient(self.api_key, self.base_api_url)
83
+
84
+ # Resolve workspace
85
+ workspace_name = workspace or "templates"
86
+ self._resolve_workspace(workspace_name)
87
+
88
+ # Build steps (starts with Orgo base image)
89
+ self._steps: List[str] = ["FROM registry.fly.io/orgo-image-repo:latest"]
90
+ self._image_ref: Optional[str] = None
91
+
92
+ if self.verbose:
93
+ _print_info(f"Template '{name}' initialized")
94
+
95
+ def _resolve_workspace(self, workspace_name: str):
96
+ """Find or create workspace for templates"""
97
+ try:
98
+ workspace = self.api.get_project_by_name(workspace_name)
99
+ self._workspace_id = workspace["id"]
100
+ self._org_id = workspace.get("user_id", "orgo")
101
+ except Exception:
102
+ if self.verbose:
103
+ _print_info(f"Creating workspace '{workspace_name}'...")
104
+ workspace = self.api.create_project(workspace_name)
105
+ self._workspace_id = workspace["id"]
106
+ self._org_id = workspace.get("user_id", "orgo")
107
+
108
+ # =========================================================================
109
+ # Build Steps (Chainable)
110
+ # =========================================================================
111
+
112
+ def run(self, command: str) -> 'Template':
113
+ """
114
+ Run a command during build (like apt-get, pip install, etc.)
115
+
116
+ Args:
117
+ command: Shell command to run
118
+
119
+ Returns:
120
+ Self for chaining
121
+
122
+ Example:
123
+ template.run("apt-get update")
124
+ template.run("pip install requests")
125
+ """
126
+ self._steps.append(f"RUN {command}")
127
+ return self
128
+
129
+ def copy(self, src: str, dest: str) -> 'Template':
130
+ """
131
+ Copy local files into the template.
132
+
133
+ Args:
134
+ src: Local file or directory path
135
+ dest: Destination path in the template
136
+
137
+ Returns:
138
+ Self for chaining
139
+
140
+ Example:
141
+ template.copy("./config.json", "/app/config.json")
142
+ template.copy("./scripts", "/app/scripts")
143
+ """
144
+ self._steps.append(f"COPY {src} {dest}")
145
+ return self
146
+
147
+ def env(self, key: str, value: str) -> 'Template':
148
+ """
149
+ Set an environment variable.
150
+
151
+ Args:
152
+ key: Environment variable name
153
+ value: Environment variable value
154
+
155
+ Returns:
156
+ Self for chaining
157
+
158
+ Example:
159
+ template.env("PYTHONPATH", "/app")
160
+ template.env("DEBUG", "true")
161
+ """
162
+ self._steps.append(f"ENV {key}={value}")
163
+ return self
164
+
165
+ def workdir(self, path: str) -> 'Template':
166
+ """
167
+ Set the working directory.
168
+
169
+ Args:
170
+ path: Directory path
171
+
172
+ Returns:
173
+ Self for chaining
174
+
175
+ Example:
176
+ template.workdir("/app")
177
+ """
178
+ self._steps.append(f"WORKDIR {path}")
179
+ return self
180
+
181
+ def clone(self, repo_url: str, dest: str = "/repo") -> 'Template':
182
+ """
183
+ Clone a git repository.
184
+
185
+ Args:
186
+ repo_url: Git repository URL
187
+ dest: Destination directory (default: /repo)
188
+
189
+ Returns:
190
+ Self for chaining
191
+
192
+ Example:
193
+ template.clone("https://github.com/user/repo.git")
194
+ template.clone("https://github.com/user/repo.git", "/app")
195
+ """
196
+ self._steps.append(f"RUN git clone {repo_url} {dest}")
197
+ return self
198
+
199
+ # =========================================================================
200
+ # Build
201
+ # =========================================================================
202
+
203
+ def build(self, context: str = ".") -> str:
204
+ """
205
+ Build and publish the template.
206
+
207
+ This builds a Docker image with your configuration and pushes it
208
+ to Orgo's registry. After building, you can launch computers from
209
+ this template using Computer(template="name").
210
+
211
+ Args:
212
+ context: Build context directory (default: current directory)
213
+
214
+ Returns:
215
+ Image reference string
216
+
217
+ Example:
218
+ template.run("pip install pandas")
219
+ image_ref = template.build()
220
+ print(f"Built: {image_ref}")
221
+ """
222
+ if len(self._steps) <= 1:
223
+ raise ValueError("No build steps defined. Use .run(), .copy(), etc. first.")
224
+
225
+ if self.verbose:
226
+ _print_info(f"Building template '{self.name}'...")
227
+
228
+ # Register build with API
229
+ try:
230
+ build_info = self.api.create_build(self._org_id, self._workspace_id, self.name)
231
+ build_data = build_info.get('build', {})
232
+
233
+ build_id = build_data.get('id')
234
+ tag = build_data.get('tag')
235
+ image_ref = build_data.get('imageRef')
236
+
237
+ if not build_id or not image_ref:
238
+ raise ValueError("Failed to register build")
239
+
240
+ if self.verbose:
241
+ _print_info(f"Build ID: {build_id}")
242
+ _print_info(f"Image: {image_ref}")
243
+
244
+ except Exception as e:
245
+ _print_error(f"Failed to register build: {e}")
246
+ raise
247
+
248
+ # Generate Dockerfile
249
+ dockerfile_path = f"Dockerfile.{tag}"
250
+ logs = []
251
+
252
+ try:
253
+ with open(dockerfile_path, "w") as f:
254
+ f.write("\n".join(self._steps))
255
+
256
+ # Update status
257
+ self.api.update_build(build_id, "building")
258
+
259
+ # Setup remote builder
260
+ builder_name = "orgo-remote"
261
+ remote_url = "tcp://orgoforge.fly.dev:1234"
262
+
263
+ try:
264
+ subprocess.run(
265
+ ["docker", "buildx", "inspect", builder_name],
266
+ check=True,
267
+ stdout=subprocess.DEVNULL,
268
+ stderr=subprocess.DEVNULL
269
+ )
270
+ except subprocess.CalledProcessError:
271
+ if self.verbose:
272
+ _print_info("Setting up build environment...")
273
+ subprocess.run([
274
+ "docker", "buildx", "create",
275
+ "--name", builder_name,
276
+ "--driver", "remote",
277
+ remote_url,
278
+ "--use"
279
+ ], check=True)
280
+ subprocess.run(["docker", "buildx", "inspect", "--bootstrap"], check=True)
281
+
282
+ # Build and push
283
+ cmd = [
284
+ "docker", "buildx", "build",
285
+ "--builder", builder_name,
286
+ "-t", image_ref,
287
+ "-f", dockerfile_path,
288
+ context,
289
+ "--push"
290
+ ]
291
+
292
+ if self.verbose:
293
+ _print_info("Building image...")
294
+
295
+ process = subprocess.Popen(
296
+ cmd,
297
+ stdout=subprocess.PIPE,
298
+ stderr=subprocess.STDOUT,
299
+ text=True,
300
+ bufsize=1
301
+ )
302
+
303
+ while True:
304
+ line = process.stdout.readline()
305
+ if not line and process.poll() is not None:
306
+ break
307
+ if line:
308
+ if self.verbose:
309
+ print(line, end="")
310
+ logs.append(line)
311
+
312
+ if process.returncode != 0:
313
+ raise Exception(f"Build failed with exit code {process.returncode}")
314
+
315
+ # Success
316
+ self.api.update_build(build_id, "completed", build_log="".join(logs))
317
+ self._image_ref = image_ref
318
+
319
+ if self.verbose:
320
+ _print_success(f"Template '{self.name}' built successfully!")
321
+
322
+ return image_ref
323
+
324
+ except Exception as e:
325
+ _print_error(f"Build failed: {e}")
326
+ try:
327
+ self.api.update_build(build_id, "failed", error_message=str(e), build_log="".join(logs))
328
+ except:
329
+ pass
330
+ raise
331
+
332
+ finally:
333
+ if os.path.exists(dockerfile_path):
334
+ os.remove(dockerfile_path)
335
+
336
+ # =========================================================================
337
+ # Properties
338
+ # =========================================================================
339
+
340
+ @property
341
+ def image_ref(self) -> Optional[str]:
342
+ """Get the built image reference (None if not built yet)."""
343
+ return self._image_ref
344
+
345
+ def __repr__(self):
346
+ status = "built" if self._image_ref else "not built"
347
+ return f"Template(name='{self.name}', status='{status}')"
orgo/utils/__init__.py CHANGED
@@ -1,6 +1,6 @@
1
- # src/orgo/utils/__init__.py
2
- """Utility functions for Orgo SDK"""
3
-
4
- from .auth import get_api_key
5
-
1
+ # src/orgo/utils/__init__.py
2
+ """Utility functions for Orgo SDK"""
3
+
4
+ from .auth import get_api_key
5
+
6
6
  __all__ = ["get_api_key"]
orgo/utils/auth.py CHANGED
@@ -1,17 +1,17 @@
1
- # src/orgo/utils/auth.py
2
- """Authentication utilities for Orgo SDK"""
3
-
4
- import os
5
- from typing import Optional
6
-
7
- def get_api_key(api_key: Optional[str] = None) -> str:
8
- """Get the Orgo API key from parameters or environment"""
9
- key = api_key or os.environ.get("ORGO_API_KEY")
10
-
11
- if not key:
12
- raise ValueError(
13
- "API key required. Set ORGO_API_KEY environment variable or pass api_key parameter. "
14
- "Get a key at https://www.orgo.ai/start"
15
- )
16
-
1
+ # src/orgo/utils/auth.py
2
+ """Authentication utilities for Orgo SDK"""
3
+
4
+ import os
5
+ from typing import Optional
6
+
7
+ def get_api_key(api_key: Optional[str] = None) -> str:
8
+ """Get the Orgo API key from parameters or environment"""
9
+ key = api_key or os.environ.get("ORGO_API_KEY")
10
+
11
+ if not key:
12
+ raise ValueError(
13
+ "API key required. Set ORGO_API_KEY environment variable or pass api_key parameter. "
14
+ "Get a key at https://www.orgo.ai/start"
15
+ )
16
+
17
17
  return key
@@ -1,47 +1,47 @@
1
- Metadata-Version: 2.4
2
- Name: orgo
3
- Version: 0.0.40
4
- Summary: Computers for AI agents
5
- Author: Orgo Team
6
- License: MIT
7
- Project-URL: Homepage, https://www.orgo.ai
8
- Project-URL: Documentation, https://docs.orgo.ai
9
- Requires-Python: >=3.7
10
- Description-Content-Type: text/markdown
11
- Requires-Dist: requests>=2.28.0
12
- Requires-Dist: pillow>=9.0.0
13
- Requires-Dist: anthropic>=0.50.0
14
- Requires-Dist: websocket-client>=1.6.0
15
-
16
- # Orgo SDK
17
-
18
- Desktop infrastructure for AI agents.
19
-
20
- ## Install
21
-
22
- ```bash
23
- pip install orgo
24
- ```
25
-
26
- ## Usage
27
-
28
- ```python
29
- from orgo import Computer
30
-
31
- # Create computer
32
- computer = Computer()
33
-
34
- # Control
35
- computer.left_click(100, 200)
36
- computer.type("Hello world")
37
- computer.key("Enter")
38
- computer.screenshot() # Returns PIL Image
39
-
40
- # Execute Python code
41
- computer.exec("import pyautogui; pyautogui.click(512, 384)")
42
-
43
- # Cleanup
44
- computer.shutdown()
45
- ```
46
-
47
- Full documentation: [docs.orgo.ai](https://docs.orgo.ai)
1
+ Metadata-Version: 2.4
2
+ Name: orgo
3
+ Version: 0.0.41
4
+ Summary: Computers for AI agents
5
+ Author: Orgo Team
6
+ License: MIT
7
+ Project-URL: Homepage, https://www.orgo.ai
8
+ Project-URL: Documentation, https://docs.orgo.ai
9
+ Requires-Python: >=3.7
10
+ Description-Content-Type: text/markdown
11
+ Requires-Dist: requests>=2.28.0
12
+ Requires-Dist: pillow>=9.0.0
13
+ Requires-Dist: anthropic>=0.50.0
14
+ Requires-Dist: websocket-client>=1.6.0
15
+
16
+ # Orgo SDK
17
+
18
+ Desktop infrastructure for AI agents.
19
+
20
+ ## Install
21
+
22
+ ```bash
23
+ pip install orgo
24
+ ```
25
+
26
+ ## Usage
27
+
28
+ ```python
29
+ from orgo import Computer
30
+
31
+ # Create computer
32
+ computer = Computer()
33
+
34
+ # Control
35
+ computer.left_click(100, 200)
36
+ computer.type("Hello world")
37
+ computer.key("Enter")
38
+ computer.screenshot() # Returns PIL Image
39
+
40
+ # Execute Python code
41
+ computer.exec("import pyautogui; pyautogui.click(512, 384)")
42
+
43
+ # Cleanup
44
+ computer.shutdown()
45
+ ```
46
+
47
+ Full documentation: [docs.orgo.ai](https://docs.orgo.ai)
@@ -0,0 +1,14 @@
1
+ orgo/__init__.py,sha256=o4kBswIVy40yNdjBs6a96EZgTJrWhzPOD_eRaRZSggM,370
2
+ orgo/computer.py,sha256=797UPugdPbAFgk2IWwW_kSenZE8RRR_6yaPe6IboB-M,20087
3
+ orgo/forge.py,sha256=Ey_X3tZwlgHPCSBYBIxPInNaERARoDZQ3vxjvOBLn_I,6714
4
+ orgo/project.py,sha256=Abn58FKAL3vety-MzHJDR-G0GFnEDSD_ljTYnXp9e1I,3148
5
+ orgo/prompt.py,sha256=EluyrgMLfgQ8C6kR4l_jKhu6mnCuSL14PGYO1AE4FKk,39685
6
+ orgo/template.py,sha256=lZJ4J5VisVUW5hN3_nsv1byNKnzZXQ-DDrIPm00C_DA,10745
7
+ orgo/api/__init__.py,sha256=9Tzb_OPJ5DH7Cg7OrHzpZZUT4ip05alpa9RLDYmnId8,113
8
+ orgo/api/client.py,sha256=5VltfI0HGS1uZjPQXMIg5U9BnIFqsnoc_EeDtnwRRo0,9125
9
+ orgo/utils/__init__.py,sha256=W4G_nwGBf_7jy0w_mfcrkllurYHSRU4B5cMTVYH_uCc,123
10
+ orgo/utils/auth.py,sha256=tPLBJY-6gdBQWLUjUbwIwxHphC3KoRT_XgP3Iykw3Mw,509
11
+ orgo-0.0.41.dist-info/METADATA,sha256=UhjQ-0PFCF53aVof3Wd_03aqyYJbE5BBzKLDwzSuJX0,894
12
+ orgo-0.0.41.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
13
+ orgo-0.0.41.dist-info/top_level.txt,sha256=q0rYtFji8GbYuhFW8A5Ab9e0j27761IKPhnL0E9xow4,5
14
+ orgo-0.0.41.dist-info/RECORD,,
@@ -1,13 +0,0 @@
1
- orgo/__init__.py,sha256=mEaj9vlEbEqf-GJlm7C9fJSafEYP42NcX3MW8ZedZpo,318
2
- orgo/computer.py,sha256=1-8tGqoOyo-gvwLFWJkWXTYRmeRP7gbC_gz1lzyrwOA,19415
3
- orgo/forge.py,sha256=EVhogsUH5Jpw-8Cw375-jYqbh-G3QZ2PEU08UuekTyQ,6890
4
- orgo/project.py,sha256=h-RZtcY2cowMxVOdJs0pF-SNHYQ_UWm9N4iZUB3U86k,3234
5
- orgo/prompt.py,sha256=4e7iTG_V8Epg6M_2BsjOtFZn37VY31L784F7sYi7N6Q,40700
6
- orgo/api/__init__.py,sha256=LwQLTr9SvEskIKJYBSTLdGCIBrn9ooWec3PEd1F1MaU,118
7
- orgo/api/client.py,sha256=WRIT9Hk02lDA-E0ukHDiYBA9vkylZEcgM4mRd2yv2k4,9370
8
- orgo/utils/__init__.py,sha256=pXHS1agLROOLJRKo82gFIF-0QwNl-qcmrWy-kYgEvO0,128
9
- orgo/utils/auth.py,sha256=8lwpnxlhraI7t96gNR1sCeKeu-eJSsTlrYy4YsdX16k,525
10
- orgo-0.0.40.dist-info/METADATA,sha256=pZq9IutUcAXlIJRzehInCL5GtjBNncUsrPyFKyk__G4,941
11
- orgo-0.0.40.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
12
- orgo-0.0.40.dist-info/top_level.txt,sha256=q0rYtFji8GbYuhFW8A5Ab9e0j27761IKPhnL0E9xow4,5
13
- orgo-0.0.40.dist-info/RECORD,,
File without changes