vibesurf 0.1.0__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 vibesurf might be problematic. Click here for more details.
- vibe_surf/__init__.py +12 -0
- vibe_surf/_version.py +34 -0
- vibe_surf/agents/__init__.py +0 -0
- vibe_surf/agents/browser_use_agent.py +1106 -0
- vibe_surf/agents/prompts/__init__.py +1 -0
- vibe_surf/agents/prompts/vibe_surf_prompt.py +176 -0
- vibe_surf/agents/report_writer_agent.py +360 -0
- vibe_surf/agents/vibe_surf_agent.py +1632 -0
- vibe_surf/backend/__init__.py +0 -0
- vibe_surf/backend/api/__init__.py +3 -0
- vibe_surf/backend/api/activity.py +243 -0
- vibe_surf/backend/api/config.py +740 -0
- vibe_surf/backend/api/files.py +322 -0
- vibe_surf/backend/api/models.py +257 -0
- vibe_surf/backend/api/task.py +300 -0
- vibe_surf/backend/database/__init__.py +13 -0
- vibe_surf/backend/database/manager.py +129 -0
- vibe_surf/backend/database/models.py +164 -0
- vibe_surf/backend/database/queries.py +922 -0
- vibe_surf/backend/database/schemas.py +100 -0
- vibe_surf/backend/llm_config.py +182 -0
- vibe_surf/backend/main.py +137 -0
- vibe_surf/backend/migrations/__init__.py +16 -0
- vibe_surf/backend/migrations/init_db.py +303 -0
- vibe_surf/backend/migrations/seed_data.py +236 -0
- vibe_surf/backend/shared_state.py +601 -0
- vibe_surf/backend/utils/__init__.py +7 -0
- vibe_surf/backend/utils/encryption.py +164 -0
- vibe_surf/backend/utils/llm_factory.py +225 -0
- vibe_surf/browser/__init__.py +8 -0
- vibe_surf/browser/agen_browser_profile.py +130 -0
- vibe_surf/browser/agent_browser_session.py +416 -0
- vibe_surf/browser/browser_manager.py +296 -0
- vibe_surf/browser/utils.py +790 -0
- vibe_surf/browser/watchdogs/__init__.py +0 -0
- vibe_surf/browser/watchdogs/action_watchdog.py +291 -0
- vibe_surf/browser/watchdogs/dom_watchdog.py +954 -0
- vibe_surf/chrome_extension/background.js +558 -0
- vibe_surf/chrome_extension/config.js +48 -0
- vibe_surf/chrome_extension/content.js +284 -0
- vibe_surf/chrome_extension/dev-reload.js +47 -0
- vibe_surf/chrome_extension/icons/convert-svg.js +33 -0
- vibe_surf/chrome_extension/icons/logo-preview.html +187 -0
- vibe_surf/chrome_extension/icons/logo.png +0 -0
- vibe_surf/chrome_extension/manifest.json +53 -0
- vibe_surf/chrome_extension/popup.html +134 -0
- vibe_surf/chrome_extension/scripts/api-client.js +473 -0
- vibe_surf/chrome_extension/scripts/main.js +491 -0
- vibe_surf/chrome_extension/scripts/markdown-it.min.js +3 -0
- vibe_surf/chrome_extension/scripts/session-manager.js +599 -0
- vibe_surf/chrome_extension/scripts/ui-manager.js +3687 -0
- vibe_surf/chrome_extension/sidepanel.html +347 -0
- vibe_surf/chrome_extension/styles/animations.css +471 -0
- vibe_surf/chrome_extension/styles/components.css +670 -0
- vibe_surf/chrome_extension/styles/main.css +2307 -0
- vibe_surf/chrome_extension/styles/settings.css +1100 -0
- vibe_surf/cli.py +357 -0
- vibe_surf/controller/__init__.py +0 -0
- vibe_surf/controller/file_system.py +53 -0
- vibe_surf/controller/mcp_client.py +68 -0
- vibe_surf/controller/vibesurf_controller.py +616 -0
- vibe_surf/controller/views.py +37 -0
- vibe_surf/llm/__init__.py +21 -0
- vibe_surf/llm/openai_compatible.py +237 -0
- vibesurf-0.1.0.dist-info/METADATA +97 -0
- vibesurf-0.1.0.dist-info/RECORD +70 -0
- vibesurf-0.1.0.dist-info/WHEEL +5 -0
- vibesurf-0.1.0.dist-info/entry_points.txt +2 -0
- vibesurf-0.1.0.dist-info/licenses/LICENSE +201 -0
- vibesurf-0.1.0.dist-info/top_level.txt +1 -0
vibe_surf/cli.py
ADDED
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
VibeSurf CLI
|
|
4
|
+
A command-line interface for VibeSurf browser automation tool.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
import glob
|
|
10
|
+
import socket
|
|
11
|
+
import platform
|
|
12
|
+
import importlib.util
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Optional
|
|
15
|
+
|
|
16
|
+
try:
|
|
17
|
+
from rich.console import Console
|
|
18
|
+
from rich.panel import Panel
|
|
19
|
+
from rich.prompt import Prompt, Confirm
|
|
20
|
+
from rich.text import Text
|
|
21
|
+
from rich import print as rprint
|
|
22
|
+
except ImportError:
|
|
23
|
+
print("Error: rich library is required. Install with: pip install rich")
|
|
24
|
+
sys.exit(1)
|
|
25
|
+
|
|
26
|
+
# Logo components with styling for rich panels
|
|
27
|
+
VIBESURF_LOGO = """
|
|
28
|
+
[white]██╗ ██╗██╗██████╗ ███████╗[/] [darkorange]███████╗██╗ ██╗██████╗ ███████╗[/]
|
|
29
|
+
[white]██║ ██║██║██╔══██╗██╔════╝[/] [darkorange]██╔════╝██║ ██║██╔══██╗██╔════╝[/]
|
|
30
|
+
[white]██║ ██║██║██████╔╝█████╗ [/] [darkorange]███████╗██║ ██║██████╔╝█████╗ [/]
|
|
31
|
+
[white]╚██╗ ██╔╝██║██╔══██╗██╔══╝ [/] [darkorange]╚════██║██║ ██║██╔══██╗██╔══╝ [/]
|
|
32
|
+
[white] ╚████╔╝ ██║██████╔╝███████╗[/] [darkorange]███████║╚██████╔╝██║ ██║██║ [/]
|
|
33
|
+
[white] ╚═══╝ ╚═╝╚═════╝ ╚══════╝[/] [darkorange]╚══════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ [/]
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
console = Console()
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def find_chrome_browser() -> Optional[str]:
|
|
40
|
+
"""Find Chrome browser executable."""
|
|
41
|
+
system = platform.system()
|
|
42
|
+
patterns = []
|
|
43
|
+
|
|
44
|
+
# Get playwright browsers path from environment variable if set
|
|
45
|
+
playwright_path = os.environ.get('PLAYWRIGHT_BROWSERS_PATH')
|
|
46
|
+
|
|
47
|
+
if system == 'Darwin': # macOS
|
|
48
|
+
if not playwright_path:
|
|
49
|
+
playwright_path = '~/Library/Caches/ms-playwright'
|
|
50
|
+
patterns = [
|
|
51
|
+
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
|
|
52
|
+
f'{playwright_path}/chromium-*/chrome-mac/Chromium.app/Contents/MacOS/Chromium',
|
|
53
|
+
'/Applications/Chromium.app/Contents/MacOS/Chromium',
|
|
54
|
+
'/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary',
|
|
55
|
+
]
|
|
56
|
+
elif system == 'Linux':
|
|
57
|
+
if not playwright_path:
|
|
58
|
+
playwright_path = '~/.cache/ms-playwright'
|
|
59
|
+
patterns = [
|
|
60
|
+
'/usr/bin/google-chrome-stable',
|
|
61
|
+
'/usr/bin/google-chrome',
|
|
62
|
+
'/usr/local/bin/google-chrome',
|
|
63
|
+
f'{playwright_path}/chromium-*/chrome-linux/chrome',
|
|
64
|
+
'/usr/bin/chromium',
|
|
65
|
+
'/usr/bin/chromium-browser',
|
|
66
|
+
'/usr/local/bin/chromium',
|
|
67
|
+
'/snap/bin/chromium',
|
|
68
|
+
'/usr/bin/google-chrome-beta',
|
|
69
|
+
'/usr/bin/google-chrome-dev',
|
|
70
|
+
]
|
|
71
|
+
elif system == 'Windows':
|
|
72
|
+
if not playwright_path:
|
|
73
|
+
playwright_path = r'%LOCALAPPDATA%\ms-playwright'
|
|
74
|
+
patterns = [
|
|
75
|
+
r'C:\Program Files\Google\Chrome\Application\chrome.exe',
|
|
76
|
+
r'C:\Program Files (x86)\Google\Chrome\Application\chrome.exe',
|
|
77
|
+
r'%LOCALAPPDATA%\Google\Chrome\Application\chrome.exe',
|
|
78
|
+
r'%PROGRAMFILES%\Google\Chrome\Application\chrome.exe',
|
|
79
|
+
r'%PROGRAMFILES(X86)%\Google\Chrome\Application\chrome.exe',
|
|
80
|
+
f'{playwright_path}\\chromium-*\\chrome-win\\chrome.exe',
|
|
81
|
+
r'C:\Program Files\Chromium\Application\chrome.exe',
|
|
82
|
+
r'C:\Program Files (x86)\Chromium\Application\chrome.exe',
|
|
83
|
+
r'%LOCALAPPDATA%\Chromium\Application\chrome.exe',
|
|
84
|
+
]
|
|
85
|
+
|
|
86
|
+
return _find_browser_from_patterns(patterns)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def find_edge_browser() -> Optional[str]:
|
|
90
|
+
"""Find Microsoft Edge browser executable."""
|
|
91
|
+
system = platform.system()
|
|
92
|
+
patterns = []
|
|
93
|
+
|
|
94
|
+
if system == 'Darwin': # macOS
|
|
95
|
+
patterns = [
|
|
96
|
+
'/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge',
|
|
97
|
+
]
|
|
98
|
+
elif system == 'Linux':
|
|
99
|
+
patterns = [
|
|
100
|
+
'/usr/bin/microsoft-edge-stable',
|
|
101
|
+
'/usr/bin/microsoft-edge',
|
|
102
|
+
'/usr/bin/microsoft-edge-beta',
|
|
103
|
+
'/usr/bin/microsoft-edge-dev',
|
|
104
|
+
]
|
|
105
|
+
elif system == 'Windows':
|
|
106
|
+
patterns = [
|
|
107
|
+
r'C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe',
|
|
108
|
+
r'C:\Program Files\Microsoft\Edge\Application\msedge.exe',
|
|
109
|
+
r'%LOCALAPPDATA%\Microsoft\Edge\Application\msedge.exe',
|
|
110
|
+
]
|
|
111
|
+
|
|
112
|
+
return _find_browser_from_patterns(patterns)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _find_browser_from_patterns(patterns: list[str]) -> Optional[str]:
|
|
116
|
+
"""Helper function to find browser from patterns."""
|
|
117
|
+
system = platform.system()
|
|
118
|
+
|
|
119
|
+
for pattern in patterns:
|
|
120
|
+
# Expand user home directory
|
|
121
|
+
expanded_pattern = Path(pattern).expanduser()
|
|
122
|
+
|
|
123
|
+
# Handle Windows environment variables
|
|
124
|
+
if system == 'Windows':
|
|
125
|
+
pattern_str = str(expanded_pattern)
|
|
126
|
+
for env_var in ['%LOCALAPPDATA%', '%PROGRAMFILES%', '%PROGRAMFILES(X86)%']:
|
|
127
|
+
if env_var in pattern_str:
|
|
128
|
+
env_key = env_var.strip('%').replace('(X86)', ' (x86)')
|
|
129
|
+
env_value = os.environ.get(env_key, '')
|
|
130
|
+
if env_value:
|
|
131
|
+
pattern_str = pattern_str.replace(env_var, env_value)
|
|
132
|
+
expanded_pattern = Path(pattern_str)
|
|
133
|
+
|
|
134
|
+
# Convert to string for glob
|
|
135
|
+
pattern_str = str(expanded_pattern)
|
|
136
|
+
|
|
137
|
+
# Check if pattern contains wildcards
|
|
138
|
+
if '*' in pattern_str:
|
|
139
|
+
# Use glob to expand the pattern
|
|
140
|
+
matches = glob.glob(pattern_str)
|
|
141
|
+
if matches:
|
|
142
|
+
# Sort matches and take the last one (alphanumerically highest version)
|
|
143
|
+
matches.sort()
|
|
144
|
+
browser_path = matches[-1]
|
|
145
|
+
if Path(browser_path).exists() and Path(browser_path).is_file():
|
|
146
|
+
return browser_path
|
|
147
|
+
else:
|
|
148
|
+
# Direct path check
|
|
149
|
+
if expanded_pattern.exists() and expanded_pattern.is_file():
|
|
150
|
+
return str(expanded_pattern)
|
|
151
|
+
|
|
152
|
+
return None
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def is_port_available(port: int) -> bool:
|
|
156
|
+
"""Check if a port is available."""
|
|
157
|
+
try:
|
|
158
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
|
159
|
+
sock.settimeout(1)
|
|
160
|
+
result = sock.connect_ex(('127.0.0.1', port))
|
|
161
|
+
return result != 0
|
|
162
|
+
except Exception:
|
|
163
|
+
return False
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def find_available_port(start_port: int) -> int:
|
|
167
|
+
"""Find the next available port starting from start_port."""
|
|
168
|
+
port = start_port
|
|
169
|
+
while port <= 65535:
|
|
170
|
+
if is_port_available(port):
|
|
171
|
+
return port
|
|
172
|
+
port += 1
|
|
173
|
+
raise RuntimeError("No available ports found")
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def select_browser() -> Optional[str]:
|
|
177
|
+
"""Interactive browser selection."""
|
|
178
|
+
console.print("\n[bold cyan]🌐 Browser Selection[/bold cyan]")
|
|
179
|
+
console.print("VibeSurf supports Chrome and Edge browsers.\n")
|
|
180
|
+
|
|
181
|
+
options = []
|
|
182
|
+
browsers = {}
|
|
183
|
+
|
|
184
|
+
# Check for Chrome
|
|
185
|
+
chrome_path = find_chrome_browser()
|
|
186
|
+
if chrome_path:
|
|
187
|
+
options.append("1")
|
|
188
|
+
browsers["1"] = ("Chrome", chrome_path)
|
|
189
|
+
console.print(f"[green]1.[/green] Chrome ([dim]{chrome_path}[/dim])")
|
|
190
|
+
|
|
191
|
+
# Check for Edge
|
|
192
|
+
edge_path = find_edge_browser()
|
|
193
|
+
if edge_path:
|
|
194
|
+
option_num = "2" if "1" not in options else "1" if not chrome_path else "2"
|
|
195
|
+
options.append(option_num)
|
|
196
|
+
browsers[option_num] = ("Edge", edge_path)
|
|
197
|
+
console.print(f"[green]{option_num}.[/green] Microsoft Edge ([dim]{edge_path}[/dim])")
|
|
198
|
+
|
|
199
|
+
# Custom browser option
|
|
200
|
+
custom_option = str(len(options) + 1)
|
|
201
|
+
options.append(custom_option)
|
|
202
|
+
console.print(f"[yellow]{custom_option}.[/yellow] Custom browser path")
|
|
203
|
+
|
|
204
|
+
# Quit option
|
|
205
|
+
quit_option = str(len(options) + 1)
|
|
206
|
+
options.append(quit_option)
|
|
207
|
+
console.print(f"[red]{quit_option}.[/red] Quit")
|
|
208
|
+
|
|
209
|
+
if not chrome_path and not edge_path:
|
|
210
|
+
console.print("\n[yellow]⚠️ No supported browsers found automatically.[/yellow]")
|
|
211
|
+
|
|
212
|
+
while True:
|
|
213
|
+
choice = Prompt.ask(
|
|
214
|
+
"\n[bold]Select a browser",
|
|
215
|
+
choices=options,
|
|
216
|
+
default="1" if options else None
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
if choice == quit_option:
|
|
220
|
+
console.print("[yellow]👋 Goodbye![/yellow]")
|
|
221
|
+
return None
|
|
222
|
+
elif choice == custom_option:
|
|
223
|
+
while True:
|
|
224
|
+
custom_path = Prompt.ask("[bold]Enter browser executable path")
|
|
225
|
+
if custom_path.strip():
|
|
226
|
+
custom_path = custom_path.strip().strip('"\'')
|
|
227
|
+
if Path(custom_path).exists() and Path(custom_path).is_file():
|
|
228
|
+
console.print(f"[green]✅ Browser found: {custom_path}[/green]")
|
|
229
|
+
return custom_path
|
|
230
|
+
else:
|
|
231
|
+
console.print(f"[red]❌ Browser not found: {custom_path}[/red]")
|
|
232
|
+
if not Confirm.ask("[yellow]Try again?[/yellow]", default=True):
|
|
233
|
+
break
|
|
234
|
+
else:
|
|
235
|
+
console.print("[red]❌ Path cannot be empty[/red]")
|
|
236
|
+
else:
|
|
237
|
+
if choice in browsers:
|
|
238
|
+
browser_name, browser_path = browsers[choice]
|
|
239
|
+
console.print(f"[green]✅ Selected {browser_name}: {browser_path}[/green]")
|
|
240
|
+
return browser_path
|
|
241
|
+
|
|
242
|
+
return None
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def configure_port() -> int:
|
|
246
|
+
"""Configure backend port."""
|
|
247
|
+
console.print("\n[bold cyan]🔌 Port Configuration[/bold cyan]")
|
|
248
|
+
|
|
249
|
+
# Get port from environment variable
|
|
250
|
+
env_port = os.environ.get('VIBESURF_BACKEND_PORT', '').strip()
|
|
251
|
+
default_port = 9335
|
|
252
|
+
|
|
253
|
+
if env_port:
|
|
254
|
+
try:
|
|
255
|
+
default_port = int(env_port)
|
|
256
|
+
except ValueError:
|
|
257
|
+
console.print(f"[yellow]⚠️ Invalid VIBESURF_BACKEND_PORT: {env_port}. Using default: {default_port}[/yellow]")
|
|
258
|
+
|
|
259
|
+
# Check if default port is available
|
|
260
|
+
if is_port_available(default_port):
|
|
261
|
+
console.print(f"[green]✅ Port {default_port} is available[/green]")
|
|
262
|
+
selected_port = default_port
|
|
263
|
+
else:
|
|
264
|
+
console.print(f"[yellow]⚠️ Port {default_port} is occupied, finding next available port...[/yellow]")
|
|
265
|
+
selected_port = find_available_port(default_port + 1)
|
|
266
|
+
console.print(f"[green]✅ Using port {selected_port}[/green]")
|
|
267
|
+
|
|
268
|
+
# Set environment variable
|
|
269
|
+
os.environ['VIBESURF_BACKEND_PORT'] = str(selected_port)
|
|
270
|
+
return selected_port
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def configure_extension_path() -> str:
|
|
274
|
+
"""Configure extension path."""
|
|
275
|
+
console.print("\n[bold cyan]🧩 Extension Configuration[/bold cyan]")
|
|
276
|
+
|
|
277
|
+
# Get extension path from environment variable
|
|
278
|
+
env_extension = os.environ.get('VIBESURF_EXTENSION', '').strip()
|
|
279
|
+
|
|
280
|
+
if env_extension and Path(env_extension).exists():
|
|
281
|
+
console.print(f"[green]✅ Using extension from environment: {env_extension}[/green]")
|
|
282
|
+
return env_extension
|
|
283
|
+
|
|
284
|
+
# Default to chrome_extension in parent directory of this file
|
|
285
|
+
default_extension = Path(__file__).parent / "chrome_extension"
|
|
286
|
+
|
|
287
|
+
if default_extension.exists():
|
|
288
|
+
extension_path = str(default_extension.resolve())
|
|
289
|
+
console.print(f"[green]✅ Using default extension: {extension_path}[/green]")
|
|
290
|
+
os.environ['VIBESURF_EXTENSION'] = extension_path
|
|
291
|
+
return extension_path
|
|
292
|
+
else:
|
|
293
|
+
console.print(f"[red]❌ Extension not found at: {default_extension}[/red]")
|
|
294
|
+
console.print("[yellow]⚠️ VibeSurf may not function properly without the extension[/yellow]")
|
|
295
|
+
return str(default_extension)
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def start_backend(port: int) -> None:
|
|
299
|
+
"""Start the VibeSurf backend."""
|
|
300
|
+
console.print(f"\n[bold cyan]🚀 Starting VibeSurf Backend on port {port}[/bold cyan]")
|
|
301
|
+
|
|
302
|
+
try:
|
|
303
|
+
import uvicorn
|
|
304
|
+
|
|
305
|
+
from vibe_surf.backend.main import app
|
|
306
|
+
|
|
307
|
+
console.print("[green]✅ Backend modules loaded successfully[/green]")
|
|
308
|
+
console.print(f"[cyan]🌍 Access VibeSurf at: http://127.0.0.1:{port}[/cyan]")
|
|
309
|
+
console.print("[yellow]📝 Press Ctrl+C to stop the server[/yellow]\n")
|
|
310
|
+
|
|
311
|
+
# Run the server
|
|
312
|
+
uvicorn.run(app, host="127.0.0.1", port=port, log_level="info")
|
|
313
|
+
|
|
314
|
+
except KeyboardInterrupt:
|
|
315
|
+
console.print("\n[yellow]🛑 Server stopped by user[/yellow]")
|
|
316
|
+
except ImportError as e:
|
|
317
|
+
console.print(f"[red]❌ Failed to import backend modules: {e}[/red]")
|
|
318
|
+
console.print("[yellow]💡 Make sure you're running from the VibeSurf project directory[/yellow]")
|
|
319
|
+
sys.exit(1)
|
|
320
|
+
except Exception as e:
|
|
321
|
+
console.print(f"[red]❌ Failed to start backend: {e}[/red]")
|
|
322
|
+
sys.exit(1)
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def main():
|
|
326
|
+
"""Main CLI entry point."""
|
|
327
|
+
try:
|
|
328
|
+
# Display logo
|
|
329
|
+
console.print(Panel(VIBESURF_LOGO, title="[bold cyan]VibeSurf CLI[/bold cyan]", border_style="cyan"))
|
|
330
|
+
console.print("[dim]A powerful browser automation tool for vibe surfing 🏄♂️[/dim]\n")
|
|
331
|
+
|
|
332
|
+
# Browser selection
|
|
333
|
+
browser_path = select_browser()
|
|
334
|
+
if not browser_path:
|
|
335
|
+
return
|
|
336
|
+
|
|
337
|
+
# Port configuration
|
|
338
|
+
port = configure_port()
|
|
339
|
+
|
|
340
|
+
# Extension configuration
|
|
341
|
+
extension_path = configure_extension_path()
|
|
342
|
+
|
|
343
|
+
# Set browser path in environment
|
|
344
|
+
os.environ['VIBESURF_BROWSER_PATH'] = browser_path
|
|
345
|
+
|
|
346
|
+
# Start backend
|
|
347
|
+
start_backend(port)
|
|
348
|
+
|
|
349
|
+
except KeyboardInterrupt:
|
|
350
|
+
console.print("\n[yellow]👋 Goodbye![/yellow]")
|
|
351
|
+
except Exception as e:
|
|
352
|
+
console.print(f"\n[red]❌ Unexpected error: {e}[/red]")
|
|
353
|
+
sys.exit(1)
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
if __name__ == "__main__":
|
|
357
|
+
main()
|
|
File without changes
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from browser_use.filesystem.file_system import FileSystem, FileSystemError, INVALID_FILENAME_ERROR_MESSAGE
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class CustomFileSystem(FileSystem):
|
|
5
|
+
async def read_file(self, full_filename: str, external_file: bool = False) -> str:
|
|
6
|
+
"""Read file content using file-specific read method and return appropriate message to LLM"""
|
|
7
|
+
if external_file:
|
|
8
|
+
try:
|
|
9
|
+
try:
|
|
10
|
+
_, extension = self._parse_filename(full_filename)
|
|
11
|
+
except Exception:
|
|
12
|
+
return f'Error: Invalid filename format {full_filename}. Must be alphanumeric with a supported extension.'
|
|
13
|
+
if extension in ['md', 'txt', 'json', 'csv']:
|
|
14
|
+
import anyio
|
|
15
|
+
|
|
16
|
+
async with await anyio.open_file(full_filename, 'r', encoding="utf-8") as f:
|
|
17
|
+
content = await f.read()
|
|
18
|
+
return f'Read from file {full_filename}.\n<content>\n{content}\n</content>'
|
|
19
|
+
elif extension == 'pdf':
|
|
20
|
+
import pypdf
|
|
21
|
+
|
|
22
|
+
reader = pypdf.PdfReader(full_filename)
|
|
23
|
+
num_pages = len(reader.pages)
|
|
24
|
+
MAX_PDF_PAGES = 10
|
|
25
|
+
extra_pages = num_pages - MAX_PDF_PAGES
|
|
26
|
+
extracted_text = ''
|
|
27
|
+
for page in reader.pages[:MAX_PDF_PAGES]:
|
|
28
|
+
extracted_text += page.extract_text()
|
|
29
|
+
extra_pages_text = f'{extra_pages} more pages...' if extra_pages > 0 else ''
|
|
30
|
+
return f'Read from file {full_filename}.\n<content>\n{extracted_text}\n{extra_pages_text}</content>'
|
|
31
|
+
else:
|
|
32
|
+
return f'Error: Cannot read file {full_filename} as {extension} extension is not supported.'
|
|
33
|
+
except FileNotFoundError:
|
|
34
|
+
return f"Error: File '{full_filename}' not found."
|
|
35
|
+
except PermissionError:
|
|
36
|
+
return f"Error: Permission denied to read file '{full_filename}'."
|
|
37
|
+
except Exception as e:
|
|
38
|
+
return f"Error: Could not read file '{full_filename}'."
|
|
39
|
+
|
|
40
|
+
if not self._is_valid_filename(full_filename):
|
|
41
|
+
return INVALID_FILENAME_ERROR_MESSAGE
|
|
42
|
+
|
|
43
|
+
file_obj = self.get_file(full_filename)
|
|
44
|
+
if not file_obj:
|
|
45
|
+
return f"File '{full_filename}' not found."
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
content = file_obj.read()
|
|
49
|
+
return f'Read from file {full_filename}.\n<content>\n{content}\n</content>'
|
|
50
|
+
except FileSystemError as e:
|
|
51
|
+
return str(e)
|
|
52
|
+
except Exception:
|
|
53
|
+
return f"Error: Could not read file '{full_filename}'."
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import logging
|
|
3
|
+
import time
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel, ConfigDict, Field, create_model
|
|
7
|
+
|
|
8
|
+
from browser_use.agent.views import ActionResult
|
|
9
|
+
from browser_use.controller.registry.service import Registry
|
|
10
|
+
from browser_use.controller.service import Controller
|
|
11
|
+
from browser_use.telemetry import MCPClientTelemetryEvent, ProductTelemetry
|
|
12
|
+
from browser_use.utils import get_browser_use_version
|
|
13
|
+
from browser_use.mcp.client import MCPClient
|
|
14
|
+
from mcp import ClientSession, StdioServerParameters, types
|
|
15
|
+
from mcp.client.stdio import stdio_client
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class VibeSurfMCPClient(MCPClient):
|
|
21
|
+
async def connect(self, timeout: int = 200) -> None:
|
|
22
|
+
"""Connect to the MCP server and discover available tools."""
|
|
23
|
+
if self._connected:
|
|
24
|
+
logger.debug(f'Already connected to {self.server_name}')
|
|
25
|
+
return
|
|
26
|
+
|
|
27
|
+
start_time = time.time()
|
|
28
|
+
error_msg = None
|
|
29
|
+
|
|
30
|
+
try:
|
|
31
|
+
logger.info(f"🔌 Connecting to MCP server '{self.server_name}': {self.command} {' '.join(self.args)}")
|
|
32
|
+
|
|
33
|
+
# Create server parameters
|
|
34
|
+
server_params = StdioServerParameters(command=self.command, args=self.args, env=self.env)
|
|
35
|
+
|
|
36
|
+
# Start stdio client in background task
|
|
37
|
+
self._stdio_task = asyncio.create_task(self._run_stdio_client(server_params))
|
|
38
|
+
|
|
39
|
+
# Wait for connection to be established
|
|
40
|
+
retries = 0
|
|
41
|
+
max_retries = timeout / 0.1 # 10 second timeout (increased for parallel test execution)
|
|
42
|
+
while not self._connected and retries < max_retries:
|
|
43
|
+
await asyncio.sleep(0.1)
|
|
44
|
+
retries += 1
|
|
45
|
+
|
|
46
|
+
if not self._connected:
|
|
47
|
+
error_msg = f"Failed to connect to MCP server '{self.server_name}' after {max_retries * 0.1} seconds"
|
|
48
|
+
raise RuntimeError(error_msg)
|
|
49
|
+
|
|
50
|
+
logger.info(f"📦 Discovered {len(self._tools)} tools from '{self.server_name}': {list(self._tools.keys())}")
|
|
51
|
+
|
|
52
|
+
except Exception as e:
|
|
53
|
+
error_msg = str(e)
|
|
54
|
+
raise
|
|
55
|
+
finally:
|
|
56
|
+
# Capture telemetry for connect action
|
|
57
|
+
duration = time.time() - start_time
|
|
58
|
+
self._telemetry.capture(
|
|
59
|
+
MCPClientTelemetryEvent(
|
|
60
|
+
server_name=self.server_name,
|
|
61
|
+
command=self.command,
|
|
62
|
+
tools_discovered=len(self._tools),
|
|
63
|
+
version=get_browser_use_version(),
|
|
64
|
+
action='connect',
|
|
65
|
+
duration_seconds=duration,
|
|
66
|
+
error_message=error_msg,
|
|
67
|
+
)
|
|
68
|
+
)
|