simplex 1.2.3__py3-none-any.whl → 1.2.5__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,323 +1,234 @@
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"
10
6
 
11
7
  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
- """
8
+ def __init__(self, api_key: str):
20
9
  self.api_key = api_key
21
- self.browser = browser
10
+ self.session_id = None
11
+ atexit.register(self.cleanup_session)
12
+
13
+ def cleanup_session(self):
14
+ if self.session_id:
15
+ try:
16
+ self.close_session(self.session_id)
17
+ except:
18
+ pass
19
+
20
+ def close_session(self):
21
+ response = requests.post(
22
+ f"{BASE_URL}/close",
23
+ headers={
24
+ 'x-api-key': self.api_key
25
+ },
26
+ data={'session_id': self.session_id}
27
+ )
28
+ self.session_id = None
29
+ return response.json()
30
+
31
+ def create_session(self):
32
+ response = requests.post(
33
+ f"{BASE_URL}/create-session",
34
+ headers={
35
+ 'x-api-key': self.api_key
36
+ }
37
+ )
38
+
39
+ response_json = response.json()
40
+ self.session_id = response_json['session_id']
41
+ livestream_url = response_json['livestream_url']
42
+
43
+ print(f"\nSession created successfully!")
44
+ print(f"Session ID: {self.session_id}")
45
+ print(f"Livestream URL: {livestream_url}\n")
46
+
47
+ return self.session_id, livestream_url
48
+
49
+ def goto(self, url: str, cdp_url: str = None):
50
+ if not cdp_url and not self.session_id:
51
+ raise ValueError(f"Must call create_session before calling action goto with url='{url}'")
22
52
 
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()
53
+ if not url.startswith('http://') and not url.startswith('https://'):
54
+ url = 'https://' + url
55
+
56
+ data = {'url': url}
57
+
58
+ if cdp_url:
59
+ data['cdp_url'] = cdp_url
27
60
  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
61
+ data['session_id'] = self.session_id
62
+
63
63
  response = requests.post(
64
- endpoint,
65
- files=files
64
+ f"{BASE_URL}/goto",
65
+ headers={
66
+ 'x-api-key': self.api_key
67
+ },
68
+ data=data
66
69
  )
70
+ print(response.json())
71
+
72
+ def click(self, element_description: str, cdp_url: str = None):
73
+ if not cdp_url and not self.session_id:
74
+ raise ValueError(f"Must call create_session before calling action click with element_description='{element_description}'")
67
75
 
76
+ data = {'element_description': element_description}
68
77
 
69
- # Print the results
70
- if response.status_code == 200:
71
- res = response.json()
72
- bbox = [int(res['x1']), int(res['y1']), int(res['x2']), int(res['y2'])]
73
-
74
- # Add overlay directly to the page if driver exists
75
- if hasattr(self, 'driver') and annotate:
76
- # Create and inject overlay element
77
- self.driver.evaluate("""
78
- (bbox) => {
79
- // Remove any existing overlay
80
- const existingOverlay = document.getElementById('simplex-bbox-overlay');
81
- if (existingOverlay) {
82
- existingOverlay.remove();
83
- }
84
-
85
- // Create new overlay
86
- const overlay = document.createElement('div');
87
- overlay.id = 'simplex-bbox-overlay';
88
- overlay.style.position = 'fixed';
89
- overlay.style.border = '2px dashed rgba(0, 255, 0, 1)';
90
- overlay.style.background = 'rgba(74, 144, 226, 0.1)';
91
- overlay.style.animation = 'marching-ants 0.5s linear infinite';
92
- overlay.style.left = bbox[0] + 'px';
93
- overlay.style.top = bbox[1] + 'px';
94
- overlay.style.width = (bbox[2] - bbox[0]) + 'px';
95
- overlay.style.height = (bbox[3] - bbox[1]) + 'px';
96
- overlay.style.pointerEvents = 'none';
97
- overlay.style.zIndex = '10000';
98
- overlay.style.margin = '0';
99
- overlay.style.padding = '0';
100
-
101
- // Add marching ants animation keyframes
102
- if (!document.querySelector('#marching-ants-keyframes')) {
103
- const style = document.createElement('style');
104
- style.id = 'marching-ants-keyframes';
105
- style.textContent = `
106
- @keyframes marching-ants {
107
- 0% { border-style: dashed; }
108
- 50% { border-style: solid; }
109
- 100% { border-style: dashed; }
110
- }
111
- `;
112
- document.head.appendChild(style);
113
- }
114
-
115
- document.body.appendChild(overlay);
116
-
117
- // Remove overlay after 3 second
118
- setTimeout(() => {
119
- overlay.remove();
120
- }, 2000);
121
- }
122
- """, bbox)
123
- self.driver.wait_for_selector('#simplex-bbox-overlay')
124
- return bbox
78
+ if cdp_url:
79
+ data['cdp_url'] = cdp_url
125
80
  else:
126
- print("[SIMPLEX] Error:", response.text)
81
+ data['session_id'] = self.session_id
127
82
 
128
- def step_to_action(self, step_description: str, state: Image.Image | None = None) -> List[List[str]]:
129
- """
130
- Convert a step description to an action
83
+ response = requests.post(
84
+ f"{BASE_URL}/click",
85
+ headers={
86
+ 'x-api-key': self.api_key
87
+ },
88
+ data=data
89
+ )
90
+ print(response.json())
131
91
 
132
- Args:
133
- step_description (str): Description of the step to convert to action
134
- screenshot (PIL.Image.Image): Screenshot of the page
135
-
136
- Returns:
137
- action (List[List[str, str]]): List of actions to perform
138
- """
139
- if state is None:
140
- state = self.take_stable_screenshot()
92
+ def type(self, text: str, cdp_url: str = None):
93
+ if not cdp_url and not self.session_id:
94
+ raise ValueError(f"Must call create_session before calling action type with text='{text}'")
95
+
96
+ data = {'text': text}
97
+
98
+ if cdp_url:
99
+ data['cdp_url'] = cdp_url
100
+ else:
101
+ data['session_id'] = self.session_id
141
102
 
142
- endpoint = f"{BASE_URL}/step_to_action"
143
-
144
- # Convert PIL Image to bytes
145
- img_byte_arr = io.BytesIO()
146
- state.save(img_byte_arr, format='PNG')
147
- img_byte_arr = img_byte_arr.getvalue()
148
-
149
- # Prepare form data
150
- files = {
151
- 'image_data': ('screenshot.png', img_byte_arr, 'image/png'),
152
- 'step': (None, step_description),
153
- 'api_key': (None, self.api_key)
154
- }
155
-
156
- # Make the request
157
103
  response = requests.post(
158
- endpoint,
159
- files=files
104
+ f"{BASE_URL}/type",
105
+ headers={
106
+ 'x-api-key': self.api_key
107
+ },
108
+ data=data
160
109
  )
161
-
162
- # Handle response
163
- if response.status_code == 200:
164
- res = response.json()
165
- actions = res.split('\n')
166
- actions = [action.split(',') for action in actions]
167
- actions = [[action.strip() for action in action_pair] for action_pair in actions]
168
- return actions
110
+ print(response.json())
111
+
112
+ def press_enter(self, cdp_url: str = None):
113
+ if not cdp_url and not self.session_id:
114
+ raise ValueError("Must call create_session before calling action press_enter")
115
+
116
+ data = {}
117
+
118
+ if cdp_url:
119
+ data['cdp_url'] = cdp_url
169
120
  else:
170
- print(f"[SIMPLEX] Error: {response.status_code}")
171
- print(response.text)
172
- return []
173
-
174
- def goto(self, url: str, new_tab: bool = False) -> None:
175
- """
176
- Navigate to a URL
177
-
178
- Args:
179
- url (str): URL to navigate to
180
- new_tab (bool): Whether to open a new tab or use the current tab
181
- """
182
- if new_tab:
183
- self.driver = self.browser.new_page()
184
- self.driver.wait_for_load_state()
185
- print(f"[SIMPLEX] Navigating to URL {url}...")
186
- self.driver.goto(url)
187
-
188
- def click(self, element_description: str, annotate: bool = True) -> None:
189
- """
190
- Click on an element
191
- """
192
- self.execute_action(["CLICK", element_description], annotate=annotate)
193
-
194
- def type(self, text: str) -> None:
195
- """
196
- Type text into an element
197
- """
198
- self.execute_action(["TYPE", text])
199
-
200
- def press_enter(self, annotate: bool = True) -> None:
201
- """
202
- Press enter
203
- """
204
- self.execute_action(["ENTER", ""], annotate=annotate)
205
-
206
- def scroll(self, scroll_amount: int, annotate: bool = True) -> None:
207
- """
208
- Scroll the page
209
- """
210
- self.execute_action(["SCROLL", scroll_amount], annotate=annotate)
211
-
212
- def wait(self, wait_time: int, annotate: bool = True) -> None:
213
- """
214
- Wait for a given amount of time
215
- """
216
- self.execute_action(["WAIT", wait_time], annotate=annotate)
217
-
218
- def execute_action(self, action: List[List[str]], state: Image.Image | None = None, annotate: bool = True) -> None:
219
- """
220
- Execute an action with playwright driver
221
-
222
- Args:
223
- action (List[List[str]]): List of actions to perform
224
- """
225
- action_type, description = action
226
- try:
227
- if action_type == "CLICK":
228
- bbox = self.extract_bbox(description, state, annotate=annotate)
229
- center_x, center_y = center_bbox(bbox)
230
- self.driver.mouse.click(center_x, center_y)
231
- print(f"[SIMPLEX] Clicked on element \"{description}\"")
232
- elif action_type == "TYPE":
233
- self.driver.keyboard.type(description)
234
- print(f"[SIMPLEX] Typed \"{description}\"")
235
-
236
- elif action_type == "ENTER":
237
- self.driver.keyboard.press("Enter")
238
- print(f"[SIMPLEX] Pressed enter")
239
-
240
- elif action_type == "SCROLL":
241
- self.driver.mouse.wheel(0, int(description))
242
- print(f"[SIMPLEX] Scrolled {description} pixels")
243
-
244
- elif action_type == "WAIT":
245
- self.driver.wait_for_timeout(int(description))
246
- print(f"[SIMPLEX] Waited {description} seconds")
247
-
248
- except Exception as e:
249
- print(f"[SIMPLEX] Error executing action: {e}")
250
- return None
251
-
252
- def do(self, step_description: str, annotate: bool = True) -> None:
253
- """
254
- Execute a step description
255
- """
256
- state = self.take_stable_screenshot()
257
- actions = self.step_to_action(step_description, state)
258
- for action in actions:
259
- self.execute_action(action, annotate=annotate)
260
-
261
- def extract_text(self, element_description: str, state: Image.Image | None = None) -> List[str] | None:
262
- """
263
- Extract an element text from the page
264
- """
265
- print(f"[SIMPLEX] Finding element \"{element_description}\"...")
266
- if state is None:
267
- state = self.take_stable_screenshot()
268
-
269
- endpoint = f"{BASE_URL}/extract-text"
270
-
271
- # Convert PIL Image to bytes
272
- img_byte_arr = io.BytesIO()
273
- state.save(img_byte_arr, format='PNG')
274
- img_byte_arr = img_byte_arr.getvalue()
275
-
276
- # Prepare multipart form data
277
- files = {
278
- 'image_data': ('screenshot.png', img_byte_arr, 'image/png'),
279
- 'element_description': (None, element_description),
280
- 'api_key': (None, self.api_key)
281
- }
282
- print("[SIMPLEX] Sending screenshot to server...")
283
-
284
- # Make the request
121
+ data['session_id'] = self.session_id
122
+
285
123
  response = requests.post(
286
- endpoint,
287
- files=files
124
+ f"{BASE_URL}/press-enter",
125
+ headers={
126
+ 'x-api-key': self.api_key
127
+ },
128
+ data=data
288
129
  )
289
-
290
- # Print the results
291
- if response.status_code == 200:
292
- res = response.json()
293
- text = res['text']
294
- return text
130
+ print(response.json())
131
+
132
+ def extract_bbox(self, element_description: str, cdp_url: str = None):
133
+ if not cdp_url and not self.session_id:
134
+ raise ValueError(f"Must call create_session before calling action extract_bbox with element_description='{element_description}'")
135
+
136
+ data = {'element_description': element_description}
137
+
138
+ if cdp_url:
139
+ data['cdp_url'] = cdp_url
295
140
  else:
296
- print("[SIMPLEX] Error:", response.text)
297
- return None
298
-
299
- def extract_image(self, element_description: str, state: Image.Image | None = None) -> Image.Image | None:
300
- """
301
- Extract an element image from the page
302
- """
303
- if state is None:
304
- state = self.take_stable_screenshot()
305
-
306
- bbox = self.extract_bbox(element_description, state)
307
- cropped_state = state.crop((bbox[0], bbox[1], bbox[2], bbox[3]))
308
- return cropped_state
309
-
310
-
311
-
312
- def take_stable_screenshot(self) -> Image.Image:
313
- """
314
- Take a screenshot after ensuring the page is in a stable state.
141
+ data['session_id'] = self.session_id
142
+
143
+ response = requests.get(
144
+ f"{BASE_URL}/extract-bbox",
145
+ headers={
146
+ 'x-api-key': self.api_key
147
+ },
148
+ params=data
149
+ )
315
150
 
316
- Returns:
317
- PIL.Image.Image: Screenshot of the current page
318
- """
319
- print("[SIMPLEX] Taking screenshot of the page...")
320
- self.driver.wait_for_load_state('networkidle')
321
- return screenshot_to_image(self.driver.screenshot())
322
-
323
-
151
+ return response.json()
152
+
153
+ def extract_text(self, element_description: str, cdp_url: str = None, use_vision: bool = False):
154
+ if not cdp_url and not self.session_id:
155
+ raise ValueError(f"Must call create_session before calling action extract_text with element_description='{element_description}'")
156
+
157
+ data = {'element_description': element_description}
158
+
159
+ if cdp_url:
160
+ data['cdp_url'] = cdp_url
161
+ else:
162
+ data['session_id'] = self.session_id
163
+
164
+ if use_vision:
165
+ data['use_vision'] = use_vision
166
+
167
+ response = requests.get(
168
+ f"{BASE_URL}/extract-text",
169
+ headers={
170
+ 'x-api-key': self.api_key
171
+ },
172
+ params=data
173
+ )
174
+ return response.json()
175
+
176
+ def extract_image(self, element_description: str, cdp_url: str = None):
177
+ if not cdp_url and not self.session_id:
178
+ raise ValueError(f"Must call create_session before calling action extract_image with element_description='{element_description}'")
179
+
180
+ data = {'element_description': element_description}
181
+
182
+ if cdp_url:
183
+ data['cdp_url'] = cdp_url
184
+ else:
185
+ data['session_id'] = self.session_id
186
+
187
+ response = requests.get(
188
+ f"{BASE_URL}/extract-image",
189
+ headers={
190
+ 'x-api-key': self.api_key
191
+ },
192
+ params=data
193
+ )
194
+ return response.json()
195
+
196
+ def scroll(self, pixels: int, cdp_url: str = None):
197
+ if not cdp_url and not self.session_id:
198
+ raise ValueError(f"Must call create_session before calling action scroll with pixels={pixels}")
199
+
200
+ data = {'pixels': pixels}
201
+
202
+ if cdp_url:
203
+ data['cdp_url'] = cdp_url
204
+ else:
205
+ data['session_id'] = self.session_id
206
+
207
+ response = requests.post(
208
+ f"{BASE_URL}/scroll",
209
+ headers={
210
+ 'x-api-key': self.api_key
211
+ },
212
+ data=data
213
+ )
214
+ return response.json()
215
+
216
+ def wait(self, milliseconds: int, cdp_url: str = None):
217
+ if not cdp_url and not self.session_id:
218
+ raise ValueError(f"Must call create_session before calling action wait with milliseconds={milliseconds}")
219
+
220
+ data = {'milliseconds': milliseconds}
221
+
222
+ if cdp_url:
223
+ data['cdp_url'] = cdp_url
224
+ else:
225
+ data['session_id'] = self.session_id
226
+
227
+ response = requests.post(
228
+ f"{BASE_URL}/wait",
229
+ headers={
230
+ 'x-api-key': self.api_key
231
+ },
232
+ data=data
233
+ )
234
+ return response.json()
@@ -1,8 +1,8 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: simplex
3
- Version: 1.2.3
3
+ Version: 1.2.5
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=BsAp0UMjgL2MKjdT8akEjDIBajaaf_VCWyvIiDn4i4g,6990
4
+ simplex/deploy/__init__.py,sha256=_JQ81F_Nu7hSAfMA691gzs6a4-8oZ-buJ9h3Au12BKw,96
5
+ simplex/deploy/push.py,sha256=hRAbtFZaECKnBljaOLQ5nzJ6hk7tZgc1c7QdgxKQFoY,6123
6
+ simplex-1.2.5.dist-info/LICENSE,sha256=Xh0SJjYZfNI71pCNMB40aKlBLLuOB0blx5xkTtufFNQ,1075
7
+ simplex-1.2.5.dist-info/METADATA,sha256=aTiiF2h8Tgs1g20UWYBuEF3jGSnvzjRYnEhaE2GCCRw,1349
8
+ simplex-1.2.5.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
9
+ simplex-1.2.5.dist-info/entry_points.txt,sha256=3veL2w3c5vxb3dm8I_M8Fs-370n1ZnvD8uu1nSsL7z8,45
10
+ simplex-1.2.5.dist-info/top_level.txt,sha256=cbMH1bYpN0A3gP-ecibPRHasHoqB-01T_2BUFS8p0CE,8
11
+ simplex-1.2.5.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=c1kWt8KvOIu-vhVmQga1BH-sRNFHvmWBrfP9t-93iBE,12101
4
- simplex/utils.py,sha256=UrD4Ena3yk0POmxxyiqMszzPbTscTCJpMP4xZFDAuOc,339
5
- simplex-1.2.3.dist-info/LICENSE,sha256=Xh0SJjYZfNI71pCNMB40aKlBLLuOB0blx5xkTtufFNQ,1075
6
- simplex-1.2.3.dist-info/METADATA,sha256=HM_H6qRoBXMEtKsO6Ish0NU5V62a7JnugRZmSvGYNMs,1114
7
- simplex-1.2.3.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
8
- simplex-1.2.3.dist-info/entry_points.txt,sha256=3veL2w3c5vxb3dm8I_M8Fs-370n1ZnvD8uu1nSsL7z8,45
9
- simplex-1.2.3.dist-info/top_level.txt,sha256=cbMH1bYpN0A3gP-ecibPRHasHoqB-01T_2BUFS8p0CE,8
10
- simplex-1.2.3.dist-info/RECORD,,