simplex 1.2.4__py3-none-any.whl → 1.2.6__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.

Potentially problematic release.


This version of simplex might be problematic. Click here for more details.

simplex/cli.py ADDED
@@ -0,0 +1,31 @@
1
+ import click
2
+ from .deploy import push_directory, run_directory
3
+ import os
4
+
5
+ @click.group()
6
+ def cli():
7
+ """Simplex CLI tool"""
8
+ pass
9
+
10
+ @cli.command()
11
+ @click.argument('directory', type=click.Path(exists=True, file_okay=False, dir_okay=True))
12
+ def push(directory):
13
+ try:
14
+ push_directory(directory)
15
+ except Exception as e:
16
+ print(f"Error running job: {e}")
17
+ raise
18
+
19
+
20
+
21
+ @cli.command()
22
+ @click.argument('directory', type=click.Path(exists=True, file_okay=False, dir_okay=True))
23
+ def run(directory):
24
+ try:
25
+ run_directory(directory)
26
+ except Exception as e:
27
+ print(f"Error running job: {e}")
28
+ raise
29
+
30
+ def main():
31
+ cli()
@@ -0,0 +1,3 @@
1
+ from .push import push_directory, run_directory
2
+
3
+ __all__ = ['push_directory', 'run_directory']
simplex/deploy/push.py ADDED
@@ -0,0 +1,208 @@
1
+ import os
2
+ import yaml
3
+ import tempfile
4
+ import zipfile
5
+ from pathlib import Path
6
+ from dotenv import load_dotenv
7
+ import requests
8
+ from typing import Optional
9
+ from datetime import datetime, timedelta
10
+ load_dotenv() # This loads the .env file
11
+
12
+ S3_URL = "https://u3mvtbirxf.us-east-1.awsapprunner.com"
13
+ JOB_URL = "https://simplex--job-scheduler-app-handler.modal.run"
14
+
15
+ def read_config(directory: str) -> dict:
16
+ """Read and validate the simplex.yaml configuration file"""
17
+ config_path = Path(directory) / "simplex.yaml"
18
+ if not config_path.exists():
19
+ raise FileNotFoundError(f"No simplex.yaml found in {directory}")
20
+
21
+ with open(config_path) as f:
22
+ return yaml.safe_load(f)
23
+
24
+
25
+ def create_archive(directory: str, config: dict) -> str:
26
+ """Create a temporary zip archive of the entire directory"""
27
+ with tempfile.NamedTemporaryFile(suffix='.zip', delete=False) as tmp:
28
+ with zipfile.ZipFile(tmp.name, 'w', zipfile.ZIP_DEFLATED) as zip_file:
29
+ base_path = Path(directory)
30
+
31
+ # Walk through all files and directories
32
+ for root, dirs, files in os.walk(directory):
33
+ for file in files:
34
+ file_path = Path(root) / file
35
+ # Get path relative to the base directory
36
+ relative_path = file_path.relative_to(base_path)
37
+ # Add file to zip with its relative path
38
+ zip_file.write(file_path, arcname=str(relative_path))
39
+
40
+ return tmp.name
41
+
42
+ def push_directory(directory: str):
43
+ """Main function to handle parsing yaml and creating zip archive"""
44
+ config = read_config(directory)
45
+ archive_path = create_archive(directory, config)
46
+
47
+ project_name = config.get('name')
48
+ if not project_name:
49
+ raise ValueError("Project name not specified in simplex.yaml")
50
+
51
+ cron_schedule = config.get("cron_schedule", None)
52
+ if not cron_schedule:
53
+ raise ValueError("Cron schedule not specified in simplex.yaml")
54
+
55
+ try:
56
+ s3_uri = upload_files_to_s3(archive_path, project_name)
57
+ except Exception as e:
58
+ print(f"Error uploading project: {e}")
59
+ raise
60
+ finally:
61
+ os.unlink(archive_path)
62
+
63
+ try:
64
+ create_job_scheduled(project_name, cron_schedule, s3_uri)
65
+ except Exception as e:
66
+ print(f"Error creating job: {e}")
67
+ raise
68
+ finally:
69
+ print("Successfully created job!")
70
+
71
+
72
+
73
+ def upload_files_to_s3(archive_path: str, project_name: str):
74
+ # Read API key from environment
75
+ api_key = os.getenv('SIMPLEX_API_KEY')
76
+ if not api_key:
77
+ raise ValueError("SIMPLEX_API_KEY environment variable not set")
78
+
79
+ url = S3_URL + "/upload_files_to_s3"
80
+
81
+ try:
82
+ # Prepare the multipart form data
83
+ files = {
84
+ 'zip_file': ('archive.zip', open(archive_path, 'rb'), 'application/zip')
85
+ }
86
+ data = {
87
+ 'project_name': project_name,
88
+ }
89
+ headers = {
90
+ 'x-api-key': api_key
91
+ }
92
+
93
+ # Make the POST request
94
+ response = requests.post(
95
+ url,
96
+ files=files,
97
+ data=data,
98
+ headers=headers
99
+ )
100
+
101
+ # Check response
102
+ response.raise_for_status()
103
+ result = response.json()
104
+ print(f"Successfully uploaded project: {result['project_name']}")
105
+ return result['s3_uri']
106
+ except Exception as e:
107
+ print(f"Error uploading project: {e}")
108
+ raise
109
+
110
+ def create_job_scheduled(project_name: str, cron_schedule: str, s3_uri: str):
111
+ """Create a new job via the API"""
112
+ # Read API key from environment
113
+ api_key = os.getenv('SIMPLEX_API_KEY')
114
+ if not api_key:
115
+ raise ValueError("SIMPLEX_API_KEY environment variable not set")
116
+
117
+ url = JOB_URL + "/api/v1/jobs/"
118
+
119
+ try:
120
+ headers = {
121
+ 'Content-Type': 'application/json',
122
+ 'X-API-Key': api_key
123
+ }
124
+
125
+ data = {
126
+ 'name': project_name,
127
+ 's3_uri': s3_uri,
128
+ 'cron_schedule': cron_schedule
129
+ }
130
+
131
+ response = requests.post(
132
+ url,
133
+ json=data,
134
+ headers=headers
135
+ )
136
+
137
+ # Check response
138
+ response.raise_for_status()
139
+ result = response.json()
140
+ print(f"Successfully created job: {result['name']}")
141
+ return result
142
+
143
+ except Exception as e:
144
+ print(f"Error creating job: {e}")
145
+ raise
146
+
147
+
148
+ def create_job_immediately(project_name: str, s3_uri: str):
149
+ """Create a new job via the API"""
150
+ # Read API key from environment
151
+ api_key = os.getenv('SIMPLEX_API_KEY')
152
+ if not api_key:
153
+ raise ValueError("SIMPLEX_API_KEY environment variable not set")
154
+
155
+ url = JOB_URL + "/api/v1/jobs/"
156
+
157
+
158
+ try:
159
+ headers = {
160
+ 'Content-Type': 'application/json',
161
+ 'X-API-Key': api_key
162
+ }
163
+
164
+ data = {
165
+ 'name': project_name,
166
+ 's3_uri': s3_uri,
167
+ 'schedule_time': (datetime.utcnow() + timedelta(minutes=2)).strftime("%Y-%m-%dT%H:%M:%SZ")
168
+ }
169
+
170
+ response = requests.post(
171
+ url,
172
+ json=data,
173
+ headers=headers
174
+ )
175
+
176
+ # Check response
177
+ response.raise_for_status()
178
+ result = response.json()
179
+ print(f"Successfully created job: {result['name']}")
180
+ return result
181
+
182
+ except Exception as e:
183
+ print(f"Error creating job: {e}")
184
+ raise
185
+
186
+ def run_directory(directory: str):
187
+ """Run a directory immediately"""
188
+ config = read_config(directory)
189
+ archive_path = create_archive(directory, config)
190
+
191
+ project_name = config.get('name')
192
+ if not project_name:
193
+ raise ValueError("Project name not specified in simplex.yaml")
194
+
195
+ try:
196
+ s3_uri = upload_files_to_s3(archive_path, project_name)
197
+ except Exception as e:
198
+ print(f"Error uploading project: {e}")
199
+ raise
200
+
201
+ finally:
202
+ os.unlink(archive_path)
203
+
204
+ try:
205
+ create_job_immediately(project_name, s3_uri)
206
+ except Exception as e:
207
+ print(f"Error creating job: {e}")
208
+ raise
simplex/simplex.py CHANGED
@@ -1,324 +1,256 @@
1
- from playwright.sync_api import Page, sync_playwright, Browser
2
- from PIL import Image
3
- import requests
4
- from typing import List
5
- import io
1
+ import os
2
+ import requests
3
+ import atexit
6
4
 
7
- from .utils import center_bbox, screenshot_to_image
8
-
9
- BASE_URL = "https://u3mvtbirxf.us-east-1.awsapprunner.com"
5
+ # BASE_URL = "https://api.simplex.sh"
6
+ BASE_URL = "http://localhost:8000"
10
7
 
11
8
  class Simplex:
12
- def __init__(self, api_key: str, browser: Browser = None):
13
- """
14
- Initialize Simplex instance
15
-
16
- Args:
17
- api_key (str): API key for authentication
18
- browser (optional): Browser instance (for Hyperbrowser integration)
19
- """
9
+ def __init__(self, api_key: str):
20
10
  self.api_key = api_key
21
- self.browser = browser
11
+ self.session_id = None
12
+ atexit.register(self.cleanup_session)
13
+
14
+ def cleanup_session(self):
15
+ if self.session_id:
16
+ try:
17
+ self.close_session(self.session_id)
18
+ except:
19
+ pass
20
+
21
+ def close_session(self):
22
+ response = requests.post(
23
+ f"{BASE_URL}/close",
24
+ headers={
25
+ 'x-api-key': self.api_key
26
+ },
27
+ data={'session_id': self.session_id}
28
+ )
29
+ self.session_id = None
30
+ return response.json()
31
+
32
+ def create_session(self):
33
+ response = requests.post(
34
+ f"{BASE_URL}/create-session",
35
+ headers={
36
+ 'x-api-key': self.api_key
37
+ }
38
+ )
39
+
40
+ response_json = response.json()
41
+ self.session_id = response_json['session_id']
42
+ livestream_url = response_json['livestream_url']
43
+
44
+ print(f"\nSession created successfully!")
45
+ print(f"Session ID: {self.session_id}")
46
+ print(f"Livestream URL: {livestream_url}\n")
47
+
48
+ return self.session_id, livestream_url
49
+
50
+ def goto(self, url: str, cdp_url: str = None):
51
+ if not cdp_url and not self.session_id:
52
+ raise ValueError(f"Must call create_session before calling action goto with url='{url}'")
22
53
 
23
- if browser is None:
24
- self.playwright = sync_playwright().start()
25
- self.browser = self.playwright.chromium.launch(headless=True)
26
- self.driver = self.browser.new_page()
54
+ if not url.startswith('http://') and not url.startswith('https://'):
55
+ url = 'https://' + url
56
+
57
+ data = {'url': url}
58
+
59
+ if cdp_url:
60
+ data['cdp_url'] = cdp_url
27
61
  else:
28
- self.playwright = None
29
- self.driver = self.browser.contexts[0].pages[0]
30
- self.driver.set_default_navigation_timeout(0)
31
- self.driver.set_default_timeout(0)
32
-
33
- def extract_bbox(self, element_description: str, state: Image.Image | None = None, annotate: bool = True) -> List[int]:
34
- """
35
- Find an element in the screenshot using the element description
36
-
37
- Args:
38
- element_description (str): Description of the element to find
39
- screenshot (PIL.Image.Image): Screenshot of the page
40
-
41
- Returns:
42
- bounding_box (tuple): [x1, y1, x2, y2] bounding box of the found element
43
- """
44
- print(f"[SIMPLEX] Finding element \"{element_description}\"...")
45
- if state is None:
46
- state = self.take_stable_screenshot()
47
-
48
- endpoint = f"{BASE_URL}/find-element"
49
-
50
- # Convert PIL Image to bytes
51
- img_byte_arr = io.BytesIO()
52
- state.save(img_byte_arr, format='PNG')
53
- img_byte_arr = img_byte_arr.getvalue()
54
-
55
- # Prepare multipart form data
56
- files = {
57
- 'image_data': ('screenshot.png', img_byte_arr, 'image/png'),
58
- 'element_description': (None, element_description),
59
- 'api_key': (None, self.api_key)
60
- }
61
- print("[SIMPLEX] Sending screenshot to server...")
62
- # Make the request
62
+ data['session_id'] = self.session_id
63
+
63
64
  response = requests.post(
64
- endpoint,
65
- files=files
65
+ f"{BASE_URL}/goto",
66
+ headers={
67
+ 'x-api-key': self.api_key
68
+ },
69
+ data=data
66
70
  )
71
+ print(response.json())
67
72
 
73
+ def click(self, element_description: str, cdp_url: str = None):
74
+ if not cdp_url and not self.session_id:
75
+ raise ValueError(f"Must call create_session before calling action click with element_description='{element_description}'")
68
76
 
69
- # Print the results
70
- print(response.status_code)
71
- if response.status_code == 200:
72
- res = response.json()
73
- bbox = [int(res['x1']), int(res['y1']), int(res['x2']), int(res['y2'])]
74
-
75
- # Add overlay directly to the page if driver exists
76
- if hasattr(self, 'driver') and annotate:
77
- # Create and inject overlay element
78
- self.driver.evaluate("""
79
- (bbox) => {
80
- // Remove any existing overlay
81
- const existingOverlay = document.getElementById('simplex-bbox-overlay');
82
- if (existingOverlay) {
83
- existingOverlay.remove();
84
- }
85
-
86
- // Create new overlay
87
- const overlay = document.createElement('div');
88
- overlay.id = 'simplex-bbox-overlay';
89
- overlay.style.position = 'fixed';
90
- overlay.style.border = '2px dashed rgba(0, 255, 0, 1)';
91
- overlay.style.background = 'rgba(74, 144, 226, 0.1)';
92
- overlay.style.animation = 'marching-ants 0.5s linear infinite';
93
- overlay.style.left = bbox[0] + 'px';
94
- overlay.style.top = bbox[1] + 'px';
95
- overlay.style.width = (bbox[2] - bbox[0]) + 'px';
96
- overlay.style.height = (bbox[3] - bbox[1]) + 'px';
97
- overlay.style.pointerEvents = 'none';
98
- overlay.style.zIndex = '10000';
99
- overlay.style.margin = '0';
100
- overlay.style.padding = '0';
101
-
102
- // Add marching ants animation keyframes
103
- if (!document.querySelector('#marching-ants-keyframes')) {
104
- const style = document.createElement('style');
105
- style.id = 'marching-ants-keyframes';
106
- style.textContent = `
107
- @keyframes marching-ants {
108
- 0% { border-style: dashed; }
109
- 50% { border-style: solid; }
110
- 100% { border-style: dashed; }
111
- }
112
- `;
113
- document.head.appendChild(style);
114
- }
115
-
116
- document.body.appendChild(overlay);
117
-
118
- // Remove overlay after 3 second
119
- setTimeout(() => {
120
- overlay.remove();
121
- }, 2000);
122
- }
123
- """, bbox)
124
- self.driver.wait_for_selector('#simplex-bbox-overlay')
125
- return bbox
77
+ data = {'element_description': element_description}
78
+
79
+ if cdp_url:
80
+ data['cdp_url'] = cdp_url
126
81
  else:
127
- print("[SIMPLEX] Error:", response.text)
82
+ data['session_id'] = self.session_id
128
83
 
129
- def step_to_action(self, step_description: str, state: Image.Image | None = None) -> List[List[str]]:
130
- """
131
- Convert a step description to an action
84
+ response = requests.post(
85
+ f"{BASE_URL}/click",
86
+ headers={
87
+ 'x-api-key': self.api_key
88
+ },
89
+ data=data
90
+ )
91
+ print(response.json())
132
92
 
133
- Args:
134
- step_description (str): Description of the step to convert to action
135
- screenshot (PIL.Image.Image): Screenshot of the page
136
-
137
- Returns:
138
- action (List[List[str, str]]): List of actions to perform
139
- """
140
- if state is None:
141
- state = self.take_stable_screenshot()
93
+ def type(self, text: str, cdp_url: str = None):
94
+ if not cdp_url and not self.session_id:
95
+ raise ValueError(f"Must call create_session before calling action type with text='{text}'")
96
+
97
+ data = {'text': text}
98
+
99
+ if cdp_url:
100
+ data['cdp_url'] = cdp_url
101
+ else:
102
+ data['session_id'] = self.session_id
142
103
 
143
- endpoint = f"{BASE_URL}/step_to_action"
144
-
145
- # Convert PIL Image to bytes
146
- img_byte_arr = io.BytesIO()
147
- state.save(img_byte_arr, format='PNG')
148
- img_byte_arr = img_byte_arr.getvalue()
149
-
150
- # Prepare form data
151
- files = {
152
- 'image_data': ('screenshot.png', img_byte_arr, 'image/png'),
153
- 'step': (None, step_description),
154
- 'api_key': (None, self.api_key)
155
- }
156
-
157
- # Make the request
158
104
  response = requests.post(
159
- endpoint,
160
- files=files
105
+ f"{BASE_URL}/type",
106
+ headers={
107
+ 'x-api-key': self.api_key
108
+ },
109
+ data=data
161
110
  )
162
-
163
- # Handle response
164
- if response.status_code == 200:
165
- res = response.json()
166
- actions = res.split('\n')
167
- actions = [action.split(',') for action in actions]
168
- actions = [[action.strip() for action in action_pair] for action_pair in actions]
169
- return actions
111
+ print(response.json())
112
+
113
+ def press_enter(self, cdp_url: str = None):
114
+ if not cdp_url and not self.session_id:
115
+ raise ValueError("Must call create_session before calling action press_enter")
116
+
117
+ data = {}
118
+
119
+ if cdp_url:
120
+ data['cdp_url'] = cdp_url
170
121
  else:
171
- print(f"[SIMPLEX] Error: {response.status_code}")
172
- print(response.text)
173
- return []
174
-
175
- def goto(self, url: str, new_tab: bool = False) -> None:
176
- """
177
- Navigate to a URL
178
-
179
- Args:
180
- url (str): URL to navigate to
181
- new_tab (bool): Whether to open a new tab or use the current tab
182
- """
183
- if new_tab:
184
- self.driver = self.browser.new_page()
185
- self.driver.wait_for_load_state()
186
- print(f"[SIMPLEX] Navigating to URL {url}...")
187
- self.driver.goto(url)
188
-
189
- def click(self, element_description: str, annotate: bool = True) -> None:
190
- """
191
- Click on an element
192
- """
193
- self.execute_action(["CLICK", element_description], annotate=annotate)
194
-
195
- def type(self, text: str) -> None:
196
- """
197
- Type text into an element
198
- """
199
- self.execute_action(["TYPE", text])
200
-
201
- def press_enter(self, annotate: bool = True) -> None:
202
- """
203
- Press enter
204
- """
205
- self.execute_action(["ENTER", ""], annotate=annotate)
206
-
207
- def scroll(self, scroll_amount: int, annotate: bool = True) -> None:
208
- """
209
- Scroll the page
210
- """
211
- self.execute_action(["SCROLL", scroll_amount], annotate=annotate)
212
-
213
- def wait(self, wait_time: int, annotate: bool = True) -> None:
214
- """
215
- Wait for a given amount of time
216
- """
217
- self.execute_action(["WAIT", wait_time], annotate=annotate)
218
-
219
- def execute_action(self, action: List[List[str]], state: Image.Image | None = None, annotate: bool = True) -> None:
220
- """
221
- Execute an action with playwright driver
222
-
223
- Args:
224
- action (List[List[str]]): List of actions to perform
225
- """
226
- action_type, description = action
227
- try:
228
- if action_type == "CLICK":
229
- bbox = self.extract_bbox(description, state, annotate=annotate)
230
- center_x, center_y = center_bbox(bbox)
231
- self.driver.mouse.click(center_x, center_y)
232
- print(f"[SIMPLEX] Clicked on element \"{description}\"")
233
- elif action_type == "TYPE":
234
- self.driver.keyboard.type(description)
235
- print(f"[SIMPLEX] Typed \"{description}\"")
236
-
237
- elif action_type == "ENTER":
238
- self.driver.keyboard.press("Enter")
239
- print(f"[SIMPLEX] Pressed enter")
240
-
241
- elif action_type == "SCROLL":
242
- self.driver.mouse.wheel(0, int(description))
243
- print(f"[SIMPLEX] Scrolled {description} pixels")
244
-
245
- elif action_type == "WAIT":
246
- self.driver.wait_for_timeout(int(description))
247
- print(f"[SIMPLEX] Waited {description} seconds")
248
-
249
- except Exception as e:
250
- print(f"[SIMPLEX] Error executing action: {e}")
251
- return None
252
-
253
- def do(self, step_description: str, annotate: bool = True) -> None:
254
- """
255
- Execute a step description
256
- """
257
- state = self.take_stable_screenshot()
258
- actions = self.step_to_action(step_description, state)
259
- for action in actions:
260
- self.execute_action(action, annotate=annotate)
261
-
262
- def extract_text(self, element_description: str, state: Image.Image | None = None) -> List[str] | None:
263
- """
264
- Extract an element text from the page
265
- """
266
- print(f"[SIMPLEX] Finding element \"{element_description}\"...")
267
- if state is None:
268
- state = self.take_stable_screenshot()
269
-
270
- endpoint = f"{BASE_URL}/extract-text"
271
-
272
- # Convert PIL Image to bytes
273
- img_byte_arr = io.BytesIO()
274
- state.save(img_byte_arr, format='PNG')
275
- img_byte_arr = img_byte_arr.getvalue()
276
-
277
- # Prepare multipart form data
278
- files = {
279
- 'image_data': ('screenshot.png', img_byte_arr, 'image/png'),
280
- 'element_description': (None, element_description),
281
- 'api_key': (None, self.api_key)
282
- }
283
- print("[SIMPLEX] Sending screenshot to server...")
284
-
285
- # Make the request
122
+ data['session_id'] = self.session_id
123
+
286
124
  response = requests.post(
287
- endpoint,
288
- files=files
125
+ f"{BASE_URL}/press-enter",
126
+ headers={
127
+ 'x-api-key': self.api_key
128
+ },
129
+ data=data
289
130
  )
290
-
291
- # Print the results
292
- if response.status_code == 200:
293
- res = response.json()
294
- text = res['text']
295
- return text
131
+ print(response.json())
132
+
133
+ def extract_bbox(self, element_description: str, cdp_url: str = None):
134
+ if not cdp_url and not self.session_id:
135
+ raise ValueError(f"Must call create_session before calling action extract_bbox with element_description='{element_description}'")
136
+
137
+ data = {'element_description': element_description}
138
+
139
+ if cdp_url:
140
+ data['cdp_url'] = cdp_url
296
141
  else:
297
- print("[SIMPLEX] Error:", response.text)
298
- return None
299
-
300
- def extract_image(self, element_description: str, state: Image.Image | None = None) -> Image.Image | None:
301
- """
302
- Extract an element image from the page
303
- """
304
- if state is None:
305
- state = self.take_stable_screenshot()
306
-
307
- bbox = self.extract_bbox(element_description, state)
308
- cropped_state = state.crop((bbox[0], bbox[1], bbox[2], bbox[3]))
309
- return cropped_state
310
-
311
-
312
-
313
- def take_stable_screenshot(self) -> Image.Image:
314
- """
315
- Take a screenshot after ensuring the page is in a stable state.
142
+ data['session_id'] = self.session_id
143
+
144
+ response = requests.get(
145
+ f"{BASE_URL}/extract-bbox",
146
+ headers={
147
+ 'x-api-key': self.api_key
148
+ },
149
+ params=data
150
+ )
316
151
 
317
- Returns:
318
- PIL.Image.Image: Screenshot of the current page
319
- """
320
- print("[SIMPLEX] Taking screenshot of the page...")
321
- self.driver.wait_for_load_state('networkidle')
322
- return screenshot_to_image(self.driver.screenshot())
323
-
324
-
152
+ return response.json()
153
+
154
+ def extract_text(self, element_description: str, cdp_url: str = None, use_vision: bool = False):
155
+ if not cdp_url and not self.session_id:
156
+ raise ValueError(f"Must call create_session before calling action extract_text with element_description='{element_description}'")
157
+
158
+ data = {'element_description': element_description}
159
+
160
+ if cdp_url:
161
+ data['cdp_url'] = cdp_url
162
+ else:
163
+ data['session_id'] = self.session_id
164
+
165
+ if use_vision:
166
+ data['use_vision'] = use_vision
167
+
168
+ response = requests.get(
169
+ f"{BASE_URL}/extract-text",
170
+ headers={
171
+ 'x-api-key': self.api_key
172
+ },
173
+ params=data
174
+ )
175
+ return response.json()
176
+
177
+ def extract_image(self, element_description: str, cdp_url: str = None):
178
+ if not cdp_url and not self.session_id:
179
+ raise ValueError(f"Must call create_session before calling action extract_image with element_description='{element_description}'")
180
+
181
+ data = {'element_description': element_description}
182
+
183
+ if cdp_url:
184
+ data['cdp_url'] = cdp_url
185
+ else:
186
+ data['session_id'] = self.session_id
187
+
188
+ response = requests.get(
189
+ f"{BASE_URL}/extract-image",
190
+ headers={
191
+ 'x-api-key': self.api_key
192
+ },
193
+ params=data
194
+ )
195
+ return response.json()
196
+
197
+ def scroll(self, pixels: float, cdp_url: str = None):
198
+ if not cdp_url and not self.session_id:
199
+ raise ValueError(f"Must call create_session before calling action scroll with pixels={pixels}")
200
+
201
+ data = {'pixels': pixels}
202
+ print(data)
203
+
204
+ if cdp_url:
205
+ data['cdp_url'] = cdp_url
206
+ else:
207
+ data['session_id'] = self.session_id
208
+
209
+ response = requests.post(
210
+ f"{BASE_URL}/scroll",
211
+ headers={
212
+ 'x-api-key': self.api_key
213
+ },
214
+ data=data
215
+ )
216
+ return response.json()
217
+
218
+ def wait(self, milliseconds: int, cdp_url: str = None):
219
+ if not cdp_url and not self.session_id:
220
+ raise ValueError(f"Must call create_session before calling action wait with milliseconds={milliseconds}")
221
+
222
+ data = {'milliseconds': milliseconds}
223
+
224
+ if cdp_url:
225
+ data['cdp_url'] = cdp_url
226
+ else:
227
+ data['session_id'] = self.session_id
228
+
229
+ response = requests.post(
230
+ f"{BASE_URL}/wait",
231
+ headers={
232
+ 'x-api-key': self.api_key
233
+ },
234
+ data=data
235
+ )
236
+ return response.json()
237
+
238
+ def run_agent(self, prompt: str, cdp_url: str = None):
239
+ if not cdp_url and not self.session_id:
240
+ raise ValueError(f"Must call create_session before calling action run_agent with and prompt='{prompt}'")
241
+
242
+ data = {'prompt': prompt}
243
+
244
+ if cdp_url:
245
+ data['cdp_url'] = cdp_url
246
+ else:
247
+ data['session_id'] = self.session_id
248
+
249
+ response = requests.post(
250
+ f"{BASE_URL}/run-agent",
251
+ headers={
252
+ 'x-api-key': self.api_key
253
+ },
254
+ data=data
255
+ )
256
+ return response.json()
@@ -1,8 +1,8 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: simplex
3
- Version: 1.2.4
3
+ Version: 1.2.6
4
4
  Summary: Official Python SDK for Simplex API
5
- Home-page: https://github.com/shreyka/simplex-python
5
+ Home-page: https://simplex.sh
6
6
  Author: Simplex Labs, Inc.
7
7
  Author-email: founders@simplex.sh
8
8
  Classifier: Programming Language :: Python :: 3
@@ -10,7 +10,7 @@ Classifier: License :: OSI Approved :: MIT License
10
10
  Classifier: Operating System :: OS Independent
11
11
  Classifier: Development Status :: 4 - Beta
12
12
  Classifier: Intended Audience :: Developers
13
- Requires-Python: >=3.6
13
+ Requires-Python: >=3.9
14
14
  Description-Content-Type: text/markdown
15
15
  License-File: LICENSE
16
16
  Requires-Dist: openai>=1.0.0
@@ -21,6 +21,14 @@ Requires-Dist: rich>=13.0.0
21
21
  Requires-Dist: prompt_toolkit>=3.0.0
22
22
  Requires-Dist: playwright>=1.0.0
23
23
  Requires-Dist: Pillow>=9.0.0
24
+ Requires-Dist: PyYAML>=6.0.1
25
+ Requires-Dist: boto3>=1.28.0
26
+ Requires-Dist: requests>=2.31.0
27
+ Requires-Dist: MainContentExtractor
28
+ Requires-Dist: langchain_core
29
+ Requires-Dist: langchain_community
30
+ Requires-Dist: langchain_openai
31
+ Requires-Dist: langchain_anthropic
24
32
  Dynamic: author
25
33
  Dynamic: author-email
26
34
  Dynamic: classifier
@@ -0,0 +1,11 @@
1
+ simplex/__init__.py,sha256=1mbM4XUk0FNW161WOkM4ayC1s_QSsaBEls6PZ0iBScY,74
2
+ simplex/cli.py,sha256=PkHt3sBKYfu0Smil4aCMoZUD-JbPw8xsBewpFcBYDKM,675
3
+ simplex/simplex.py,sha256=qZPxheNfSl8gL_CST2br7K5pqRsdVgct5TBJa5vjsYE,7650
4
+ simplex/deploy/__init__.py,sha256=_JQ81F_Nu7hSAfMA691gzs6a4-8oZ-buJ9h3Au12BKw,96
5
+ simplex/deploy/push.py,sha256=hRAbtFZaECKnBljaOLQ5nzJ6hk7tZgc1c7QdgxKQFoY,6123
6
+ simplex-1.2.6.dist-info/LICENSE,sha256=Xh0SJjYZfNI71pCNMB40aKlBLLuOB0blx5xkTtufFNQ,1075
7
+ simplex-1.2.6.dist-info/METADATA,sha256=HgZl9RcXCuKt9eY_6J_pZBBLisTjgtO02OSEA8NTubM,1349
8
+ simplex-1.2.6.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
9
+ simplex-1.2.6.dist-info/entry_points.txt,sha256=3veL2w3c5vxb3dm8I_M8Fs-370n1ZnvD8uu1nSsL7z8,45
10
+ simplex-1.2.6.dist-info/top_level.txt,sha256=cbMH1bYpN0A3gP-ecibPRHasHoqB-01T_2BUFS8p0CE,8
11
+ simplex-1.2.6.dist-info/RECORD,,
simplex/constants.py DELETED
@@ -1 +0,0 @@
1
- BASE_URL = "https://u3mvtbirxf.us-east-1.awsapprunner.com"
simplex/utils.py DELETED
@@ -1,12 +0,0 @@
1
- from typing import List
2
- from PIL import Image
3
- import io
4
- def center_bbox(bbox: List[int]) -> List[int]:
5
- """
6
- Calculate the center coordinates of a bounding box
7
- """
8
- return [(bbox[0] + bbox[2]) // 2, (bbox[1] + bbox[3]) // 2]
9
-
10
-
11
- def screenshot_to_image(screenshot: bytes) -> Image:
12
- return Image.open(io.BytesIO(screenshot))
@@ -1,10 +0,0 @@
1
- simplex/__init__.py,sha256=1mbM4XUk0FNW161WOkM4ayC1s_QSsaBEls6PZ0iBScY,74
2
- simplex/constants.py,sha256=nIXF2oVNNNknXweXAlmE-KBM9QjJtYw9osXVYjvloN0,59
3
- simplex/simplex.py,sha256=m5vQxhVCyeU-h-cOVmpaAMoTATPJKdecfp1Fv51uaL0,12137
4
- simplex/utils.py,sha256=UrD4Ena3yk0POmxxyiqMszzPbTscTCJpMP4xZFDAuOc,339
5
- simplex-1.2.4.dist-info/LICENSE,sha256=Xh0SJjYZfNI71pCNMB40aKlBLLuOB0blx5xkTtufFNQ,1075
6
- simplex-1.2.4.dist-info/METADATA,sha256=YyDn-TaMU_3JQO9ZWFbRA214_Lak1zKmA8beIHl0zPI,1114
7
- simplex-1.2.4.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
8
- simplex-1.2.4.dist-info/entry_points.txt,sha256=3veL2w3c5vxb3dm8I_M8Fs-370n1ZnvD8uu1nSsL7z8,45
9
- simplex-1.2.4.dist-info/top_level.txt,sha256=cbMH1bYpN0A3gP-ecibPRHasHoqB-01T_2BUFS8p0CE,8
10
- simplex-1.2.4.dist-info/RECORD,,