orgo 0.0.32__py3-none-any.whl → 0.0.34__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 CHANGED
@@ -3,5 +3,6 @@
3
3
 
4
4
  from .project import Project
5
5
  from .computer import Computer
6
+ from .forge import Forge
6
7
 
7
- __all__ = ["Project", "Computer"]
8
+ __all__ = ["Project", "Computer", "Forge"]
orgo/api/client.py CHANGED
@@ -28,6 +28,11 @@ class ApiClient:
28
28
  else:
29
29
  response = self.session.request(method, url, json=data)
30
30
 
31
+ # Handle 405 specifically for better debugging
32
+ if response.status_code == 405:
33
+ logger.error(f"Method Not Allowed: {method} {url}")
34
+ logger.error(f"Response: {response.text}")
35
+
31
36
  response.raise_for_status()
32
37
  return response.json()
33
38
  except requests.exceptions.RequestException as e:
@@ -75,16 +80,20 @@ class ApiClient:
75
80
  # Computer methods
76
81
  def create_computer(self, project_id: str, computer_name: str,
77
82
  os: str = "linux", ram: int = 2, cpu: int = 2,
78
- gpu: str = "none") -> Dict[str, Any]:
83
+ gpu: str = "none", image: Optional[str] = None) -> Dict[str, Any]:
79
84
  """Create a new computer within a project"""
80
- return self._request("POST", "computers", {
85
+ data = {
81
86
  "project_id": project_id,
82
87
  "name": computer_name,
83
88
  "os": os,
84
89
  "ram": ram,
85
90
  "cpu": cpu,
86
91
  "gpu": gpu
87
- })
92
+ }
93
+ if image:
94
+ data["image"] = image
95
+
96
+ return self._request("POST", "computers", data)
88
97
 
89
98
  def list_computers(self, project_id: str) -> List[Dict[str, Any]]:
90
99
  """List all computers in a project"""
@@ -180,4 +189,39 @@ class ApiClient:
180
189
 
181
190
  def get_stream_status(self, computer_id: str) -> Dict[str, Any]:
182
191
  """Get current stream status"""
183
- return self._request("GET", f"computers/{computer_id}/stream/status")
192
+ return self._request("GET", f"computers/{computer_id}/stream/status")
193
+
194
+ # Build methods
195
+ def create_build(self, org_id: str, project_id: str, name: Optional[str] = None) -> Dict[str, Any]:
196
+ """Register a new build"""
197
+ data = {
198
+ "orgId": org_id,
199
+ "projectId": project_id
200
+ }
201
+ if name:
202
+ data["name"] = name
203
+
204
+ return self._request("POST", "builds/create", data)
205
+
206
+ def get_latest_build(self, org_id: str, project_id: str, name: str) -> Dict[str, Any]:
207
+ """Get latest completed build by name"""
208
+ return self._request("GET", "builds/latest", {
209
+ "orgId": org_id,
210
+ "projectId": project_id,
211
+ "name": name
212
+ })
213
+
214
+ def update_build(self, build_id: str, status: str,
215
+ error_message: Optional[str] = None,
216
+ build_log: Optional[str] = None) -> Dict[str, Any]:
217
+ """Update build status"""
218
+ data = {
219
+ "buildId": build_id,
220
+ "status": status
221
+ }
222
+ if error_message:
223
+ data["errorMessage"] = error_message
224
+ if build_log:
225
+ data["buildLog"] = build_log
226
+
227
+ return self._request("POST", "builds/update", data)
orgo/computer.py CHANGED
@@ -25,7 +25,8 @@ class Computer:
25
25
  memory: Optional[Literal[1, 2, 4, 8, 16, 32, 64]] = None,
26
26
  cpu: Optional[Literal[1, 2, 4, 8, 16]] = None,
27
27
  os: Optional[Literal["linux", "windows"]] = None,
28
- gpu: Optional[Literal["none", "a10", "l40s", "a100-40gb", "a100-80gb"]] = None):
28
+ gpu: Optional[Literal["none", "a10", "l40s", "a100-40gb", "a100-80gb"]] = None,
29
+ image: Optional[Union[str, Any]] = None):
29
30
  """
30
31
  Initialize an Orgo virtual computer.
31
32
 
@@ -39,11 +40,16 @@ class Computer:
39
40
  cpu: CPU cores (1, 2, 4, 8, or 16) - only used when creating
40
41
  os: Operating system ("linux" or "windows") - only used when creating
41
42
  gpu: GPU type - only used when creating
43
+ image: Custom image reference (str) or Forge object - only used when creating
42
44
 
43
45
  Examples:
44
46
  # Create computer in new project
45
47
  computer = Computer(ram=4, cpu=2)
46
48
 
49
+ # Create computer with custom image
50
+ forge = Forge(org_id="myorg", project_id="myproj").base("ubuntu").run("echo hello")
51
+ computer = Computer(image=forge)
52
+
47
53
  # Create computer in existing project
48
54
  computer = Computer(project="manus", ram=4, cpu=2)
49
55
 
@@ -64,7 +70,14 @@ class Computer:
64
70
  self.cpu = cpu or 2
65
71
  self.gpu = gpu or "none"
66
72
 
73
+ # Handle image
74
+ self.image = image
75
+ if hasattr(self.image, 'build') and callable(self.image.build):
76
+ logger.info("Building image from Forge object...")
77
+ self.image = self.image.build()
78
+
67
79
  if computer_id:
80
+
68
81
  # Just store the computer ID, no API call needed
69
82
  self.computer_id = computer_id
70
83
  self.name = name
@@ -177,6 +190,28 @@ class Computer:
177
190
  raise ValueError("os must be either 'linux' or 'windows'")
178
191
  if self.gpu not in ["none", "a10", "l40s", "a100-40gb", "a100-80gb"]:
179
192
  raise ValueError("gpu must be one of: 'none', 'a10', 'l40s', 'a100-40gb', 'a100-80gb'")
193
+
194
+ # Resolve image name if needed
195
+ image_ref = self.image
196
+ if image_ref and isinstance(image_ref, str) and not image_ref.startswith("registry.fly.io"):
197
+ logger.info(f"Resolving image name '{image_ref}'...")
198
+ try:
199
+ # Try to get org_id from project info
200
+ project_info = self.api.get_project(project_id)
201
+ org_id = project_info.get("org_id", "orgo") # Default to 'orgo'
202
+
203
+ response = self.api.get_latest_build(org_id, project_id, image_ref)
204
+ if response and response.get("build"):
205
+ resolved_ref = response.get("build", {}).get("imageRef")
206
+ if resolved_ref:
207
+ logger.info(f"Resolved '{image_ref}' to '{resolved_ref}'")
208
+ image_ref = resolved_ref
209
+ else:
210
+ logger.warning(f"Build found for '{image_ref}' but no imageRef present.")
211
+ else:
212
+ logger.warning(f"Could not resolve image name '{self.image}'. Using as is.")
213
+ except Exception as e:
214
+ logger.warning(f"Failed to resolve image name: {e}")
180
215
 
181
216
  computer = self.api.create_computer(
182
217
  project_id=project_id,
@@ -184,7 +219,8 @@ class Computer:
184
219
  os=self.os,
185
220
  ram=self.ram,
186
221
  cpu=self.cpu,
187
- gpu=self.gpu
222
+ gpu=self.gpu,
223
+ image=image_ref
188
224
  )
189
225
  self.computer_id = computer.get("id")
190
226
  logger.info(f"Created new computer {self.name} (ID: {self.computer_id})")
@@ -290,7 +326,7 @@ class Computer:
290
326
  def prompt(self,
291
327
  instruction: str,
292
328
  provider: str = "anthropic",
293
- model: str = "claude-3-7-sonnet-20250219",
329
+ model: str = "claude-sonnet-4-5-20250929",
294
330
  display_width: int = 1024,
295
331
  display_height: int = 768,
296
332
  callback: Optional[Callable[[str, Any], None]] = None,
orgo/forge.py ADDED
@@ -0,0 +1,176 @@
1
+ import os
2
+ import subprocess
3
+ import logging
4
+ from typing import Optional, List, Union
5
+ import uuid
6
+
7
+ from .api.client import ApiClient
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+ class Forge:
12
+ def __init__(self,
13
+ org_id: str,
14
+ project_id: str,
15
+ name: Optional[str] = None,
16
+ api_key: Optional[str] = None,
17
+ base_api_url: Optional[str] = None):
18
+ """
19
+ Initialize Orgo Forge for building images.
20
+
21
+ Args:
22
+ org_id: Organization ID
23
+ project_id: Project ID
24
+ name: Optional friendly name for the image (not used for registry tag currently, but for reference)
25
+ api_key: Orgo API key
26
+ base_api_url: Custom API URL
27
+ """
28
+ self.org_id = org_id
29
+ self.project_id = project_id
30
+ self.name = name
31
+ self.api_key = api_key or os.environ.get("ORGO_API_KEY")
32
+ self.base_api_url = base_api_url
33
+ self.api = ApiClient(self.api_key, self.base_api_url)
34
+
35
+ # Enforce base image
36
+ self.steps: List[str] = ["FROM registry.fly.io/orgo-image-repo:latest"]
37
+
38
+ def base(self, image: str) -> 'Forge':
39
+ """
40
+ DEPRECATED: Base image is now enforced to ensure CUA functionality.
41
+ This method will log a warning and ignore the provided image.
42
+ """
43
+ logger.warning("Forge.base() is deprecated. The base image is enforced to 'registry.fly.io/orgo-image-repo:latest'. Ignoring provided image.")
44
+ return self
45
+
46
+ def run(self, command: str) -> 'Forge':
47
+ """Run a command (RUN instruction)"""
48
+ self.steps.append(f"RUN {command}")
49
+ return self
50
+
51
+ def copy(self, src: str, dest: str) -> 'Forge':
52
+ """Copy files (COPY instruction)"""
53
+ self.steps.append(f"COPY {src} {dest}")
54
+ return self
55
+
56
+ def git_clone(self, url: str, dest: str) -> 'Forge':
57
+ """Clone a git repository"""
58
+ # Ensure git is installed or use a base image with git
59
+ self.steps.append(f"RUN git clone {url} {dest}")
60
+ return self
61
+
62
+ def env(self, key: str, value: str) -> 'Forge':
63
+ """Set environment variable (ENV instruction)"""
64
+ self.steps.append(f"ENV {key}={value}")
65
+ return self
66
+
67
+ def workdir(self, path: str) -> 'Forge':
68
+ """Set working directory (WORKDIR instruction)"""
69
+ self.steps.append(f"WORKDIR {path}")
70
+ return self
71
+
72
+ def build(self, context_path: str = ".", push: bool = True) -> str:
73
+ """
74
+ Execute the build.
75
+
76
+ 1. Registers build with Orgo API to get unique tag.
77
+ 2. Generates Dockerfile.
78
+ 3. Runs docker build (requires docker CLI installed).
79
+ 4. Pushes to registry.
80
+ 5. Updates build status.
81
+
82
+ Returns:
83
+ str: The full image reference (e.g. registry.fly.io/app:tag)
84
+ """
85
+ if not self.steps:
86
+ raise ValueError("No build steps defined. Use .base() to start.")
87
+
88
+ # 1. Register build
89
+ logger.info("Registering build with Orgo API...")
90
+ try:
91
+ build_info = self.api.create_build(self.org_id, self.project_id, self.name)
92
+ build_data = build_info.get('build', {})
93
+
94
+ build_id = build_data.get('id')
95
+ tag = build_data.get('tag')
96
+ image_ref = build_data.get('imageRef')
97
+ # buildkit_url = build_data.get('buildkitUrl') # Not used for local docker build yet
98
+
99
+ if not build_id or not image_ref:
100
+ raise ValueError("Failed to get build ID or image ref from API")
101
+
102
+ logger.info(f"Build registered. ID: {build_id}, Tag: {tag}")
103
+ logger.info(f"Target Image: {image_ref}")
104
+
105
+ except Exception as e:
106
+ logger.error(f"Failed to register build: {e}")
107
+ raise e
108
+
109
+ # 2. Generate Dockerfile
110
+ dockerfile_path = f"Dockerfile.{tag}"
111
+ try:
112
+ with open(dockerfile_path, "w") as f:
113
+ f.write("\n".join(self.steps))
114
+
115
+ # 3. Execute Build
116
+ logger.info("Starting build...")
117
+ self.api.update_build(build_id, "building")
118
+
119
+ # Use remote builder
120
+ builder_name = "orgo-remote"
121
+ remote_url = "tcp://orgoforge.fly.dev:1234"
122
+
123
+ # Check if builder exists
124
+ try:
125
+ subprocess.run(["docker", "buildx", "inspect", builder_name], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
126
+ except subprocess.CalledProcessError:
127
+ logger.info(f"Creating remote builder '{builder_name}' pointing to {remote_url}...")
128
+ subprocess.run(["docker", "buildx", "create", "--name", builder_name, "--driver", "remote", remote_url, "--use"], check=True)
129
+ subprocess.run(["docker", "buildx", "inspect", "--bootstrap"], check=True)
130
+
131
+ cmd = ["docker", "buildx", "build", "--builder", builder_name, "-t", image_ref, "-f", dockerfile_path, context_path]
132
+ if push:
133
+ cmd.append("--push")
134
+
135
+ # Run build
136
+ process = subprocess.Popen(
137
+ cmd,
138
+ stdout=subprocess.PIPE,
139
+ stderr=subprocess.STDOUT,
140
+ text=True,
141
+ bufsize=1,
142
+ universal_newlines=True
143
+ )
144
+
145
+ logs = []
146
+ while True:
147
+ line = process.stdout.readline()
148
+ if not line and process.poll() is not None:
149
+ break
150
+ if line:
151
+ print(line, end="") # Stream to console
152
+ logs.append(line)
153
+
154
+ if process.returncode != 0:
155
+ raise Exception(f"Docker build failed with exit code {process.returncode}")
156
+
157
+ # 5. Report Success
158
+ full_log = "".join(logs)
159
+ self.api.update_build(build_id, "completed", build_log=full_log)
160
+ logger.info("Build completed successfully!")
161
+
162
+ return image_ref
163
+
164
+ except Exception as e:
165
+ logger.error(f"Build failed: {e}")
166
+ full_log = "".join(logs) if 'logs' in locals() else str(e)
167
+ try:
168
+ self.api.update_build(build_id, "failed", error_message=str(e), build_log=full_log)
169
+ except:
170
+ pass # Ignore error reporting failure
171
+ raise e
172
+
173
+ finally:
174
+ if os.path.exists(dockerfile_path):
175
+ os.remove(dockerfile_path)
176
+
orgo/project.py CHANGED
@@ -66,7 +66,7 @@ class Project:
66
66
 
67
67
  def list_computers(self) -> List[Dict[str, Any]]:
68
68
  """List all computers in this project"""
69
- return self.api.list_computers(self.name)
69
+ return self.api.list_computers(self.id)
70
70
 
71
71
  def get_computer(self, computer_name: str = None) -> Optional[Dict[str, Any]]:
72
72
  """Get a specific computer in this project by name, or the first one if no name specified"""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: orgo
3
- Version: 0.0.32
3
+ Version: 0.0.34
4
4
  Summary: Computers for AI agents
5
5
  Author: Orgo Team
6
6
  License: MIT
@@ -0,0 +1,13 @@
1
+ orgo/__init__.py,sha256=7uMYbhVNVUtrH7mCd8Ofll36EIdR92LcVlF7bdKuVR4,206
2
+ orgo/computer.py,sha256=8Vz6MhBnS4yd8ibUt7JpOs6sUlUpbv4uy18o569KvYg,16100
3
+ orgo/forge.py,sha256=Ey_X3tZwlgHPCSBYBIxPInNaERARoDZQ3vxjvOBLn_I,6714
4
+ orgo/project.py,sha256=Abn58FKAL3vety-MzHJDR-G0GFnEDSD_ljTYnXp9e1I,3148
5
+ orgo/prompt.py,sha256=ynblwXPTDp_aF1MbGBsY0PIEr9naklDaKFcfSE_EZ6E,19781
6
+ orgo/api/__init__.py,sha256=9Tzb_OPJ5DH7Cg7OrHzpZZUT4ip05alpa9RLDYmnId8,113
7
+ orgo/api/client.py,sha256=VGdlBCu2gAdDwMZ55n7kQS4R-CFXJjLByXPmRlMLoiY,9097
8
+ orgo/utils/__init__.py,sha256=W4G_nwGBf_7jy0w_mfcrkllurYHSRU4B5cMTVYH_uCc,123
9
+ orgo/utils/auth.py,sha256=tPLBJY-6gdBQWLUjUbwIwxHphC3KoRT_XgP3Iykw3Mw,509
10
+ orgo-0.0.34.dist-info/METADATA,sha256=nKZ6fmjASvp3ORb2-SJmoACreppK86jjoWOts57am8E,822
11
+ orgo-0.0.34.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
12
+ orgo-0.0.34.dist-info/top_level.txt,sha256=q0rYtFji8GbYuhFW8A5Ab9e0j27761IKPhnL0E9xow4,5
13
+ orgo-0.0.34.dist-info/RECORD,,
@@ -1,12 +0,0 @@
1
- orgo/__init__.py,sha256=aw3BM7-Wy8jk-mvIWRG2gC4-nsc74s6ZFm1U21NyGeM,171
2
- orgo/computer.py,sha256=np8g67-ASCci3EmtQweCpOjDzeUMkXfAY9wve9s7tCc,14268
3
- orgo/project.py,sha256=uVDFa8iyn5OaHzTzjGQhxnF_nVzwkqkqUShiV3M0AWU,3150
4
- orgo/prompt.py,sha256=ynblwXPTDp_aF1MbGBsY0PIEr9naklDaKFcfSE_EZ6E,19781
5
- orgo/api/__init__.py,sha256=9Tzb_OPJ5DH7Cg7OrHzpZZUT4ip05alpa9RLDYmnId8,113
6
- orgo/api/client.py,sha256=apny7V3IYJTyDwn5utukzyECLWT65oo-1EmFRwHL--E,7544
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.32.dist-info/METADATA,sha256=w3RNXo5BW2YlXUgUb1TzkXaJ50RpkPd0Zd-vOllwx_A,822
10
- orgo-0.0.32.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
11
- orgo-0.0.32.dist-info/top_level.txt,sha256=q0rYtFji8GbYuhFW8A5Ab9e0j27761IKPhnL0E9xow4,5
12
- orgo-0.0.32.dist-info/RECORD,,
File without changes