vibesurf 0.1.35__py3-none-any.whl → 0.1.37__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.
- vibe_surf/_version.py +2 -2
- vibe_surf/agents/browser_use_agent.py +14 -276
- vibe_surf/agents/report_writer_agent.py +21 -1
- vibe_surf/agents/vibe_surf_agent.py +61 -2
- vibe_surf/backend/llm_config.py +27 -0
- vibe_surf/backend/shared_state.py +26 -26
- vibe_surf/backend/utils/encryption.py +40 -4
- vibe_surf/backend/utils/llm_factory.py +16 -0
- vibe_surf/browser/agen_browser_profile.py +5 -0
- vibe_surf/browser/agent_browser_session.py +116 -25
- vibe_surf/browser/watchdogs/action_watchdog.py +1 -83
- vibe_surf/browser/watchdogs/dom_watchdog.py +9 -6
- vibe_surf/cli.py +52 -4
- vibe_surf/llm/openai_compatible.py +2 -9
- vibe_surf/telemetry/views.py +32 -0
- vibe_surf/tools/browser_use_tools.py +39 -42
- vibe_surf/tools/file_system.py +5 -2
- vibe_surf/tools/utils.py +118 -0
- vibe_surf/tools/vibesurf_tools.py +44 -236
- vibe_surf/tools/views.py +1 -1
- {vibesurf-0.1.35.dist-info → vibesurf-0.1.37.dist-info}/METADATA +12 -2
- {vibesurf-0.1.35.dist-info → vibesurf-0.1.37.dist-info}/RECORD +26 -25
- {vibesurf-0.1.35.dist-info → vibesurf-0.1.37.dist-info}/WHEEL +0 -0
- {vibesurf-0.1.35.dist-info → vibesurf-0.1.37.dist-info}/entry_points.txt +0 -0
- {vibesurf-0.1.35.dist-info → vibesurf-0.1.37.dist-info}/licenses/LICENSE +0 -0
- {vibesurf-0.1.35.dist-info → vibesurf-0.1.37.dist-info}/top_level.txt +0 -0
|
@@ -41,6 +41,7 @@ from vibe_surf.browser.browser_manager import BrowserManager
|
|
|
41
41
|
from vibe_surf.tools.vibesurf_registry import VibeSurfRegistry
|
|
42
42
|
from bs4 import BeautifulSoup
|
|
43
43
|
from vibe_surf.logger import get_logger
|
|
44
|
+
from vibe_surf.tools.utils import clean_html_basic
|
|
44
45
|
|
|
45
46
|
logger = get_logger(__name__)
|
|
46
47
|
|
|
@@ -49,123 +50,6 @@ Context = TypeVar('Context')
|
|
|
49
50
|
T = TypeVar('T', bound=BaseModel)
|
|
50
51
|
|
|
51
52
|
|
|
52
|
-
def clean_html_basic(page_html_content, max_text_length=100):
|
|
53
|
-
soup = BeautifulSoup(page_html_content, 'html.parser')
|
|
54
|
-
|
|
55
|
-
for script in soup(["script", "style"]):
|
|
56
|
-
script.decompose()
|
|
57
|
-
|
|
58
|
-
from bs4 import Comment
|
|
59
|
-
comments = soup.findAll(text=lambda text: isinstance(text, Comment))
|
|
60
|
-
for comment in comments:
|
|
61
|
-
comment.extract()
|
|
62
|
-
|
|
63
|
-
for text_node in soup.find_all(string=True):
|
|
64
|
-
if text_node.parent.name not in ['script', 'style']:
|
|
65
|
-
clean_text = ' '.join(text_node.split())
|
|
66
|
-
|
|
67
|
-
if len(clean_text) > max_text_length:
|
|
68
|
-
clean_text = clean_text[:max_text_length].rstrip() + "..."
|
|
69
|
-
|
|
70
|
-
if clean_text != text_node:
|
|
71
|
-
text_node.replace_with(clean_text)
|
|
72
|
-
|
|
73
|
-
important_attrs = ['id', 'class', 'name', 'role', 'type',
|
|
74
|
-
'colspan', 'rowspan', 'headers', 'scope',
|
|
75
|
-
'href', 'src', 'alt', 'title']
|
|
76
|
-
|
|
77
|
-
for tag in soup.find_all():
|
|
78
|
-
attrs_to_keep = {}
|
|
79
|
-
for attr in list(tag.attrs.keys()):
|
|
80
|
-
if (attr in important_attrs or
|
|
81
|
-
attr.startswith('data-') or
|
|
82
|
-
attr.startswith('aria-')):
|
|
83
|
-
attrs_to_keep[attr] = tag.attrs[attr]
|
|
84
|
-
tag.attrs = attrs_to_keep
|
|
85
|
-
|
|
86
|
-
return str(soup)
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
def get_sibling_position(node: EnhancedDOMTreeNode) -> int:
|
|
90
|
-
"""Get the position of node among its siblings with the same tag"""
|
|
91
|
-
if not node.parent_node:
|
|
92
|
-
return 1
|
|
93
|
-
|
|
94
|
-
tag_name = node.tag_name
|
|
95
|
-
position = 1
|
|
96
|
-
|
|
97
|
-
# Find siblings with same tag name before this node
|
|
98
|
-
for sibling in node.parent_node.children:
|
|
99
|
-
if sibling == node:
|
|
100
|
-
break
|
|
101
|
-
if sibling.tag_name == tag_name:
|
|
102
|
-
position += 1
|
|
103
|
-
|
|
104
|
-
return position
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
def extract_css_hints(node: EnhancedDOMTreeNode) -> dict:
|
|
108
|
-
"""Extract CSS selector construction hints"""
|
|
109
|
-
hints = {}
|
|
110
|
-
|
|
111
|
-
if "id" in node.attributes:
|
|
112
|
-
hints["id"] = f"#{node.attributes['id']}"
|
|
113
|
-
|
|
114
|
-
if "class" in node.attributes:
|
|
115
|
-
classes = node.attributes["class"].split()
|
|
116
|
-
hints["class"] = f".{'.'.join(classes[:3])}" # Limit class count
|
|
117
|
-
|
|
118
|
-
# Attribute selector hints
|
|
119
|
-
for attr in ["name", "data-testid", "type"]:
|
|
120
|
-
if attr in node.attributes:
|
|
121
|
-
hints[f"attr_{attr}"] = f"[{attr}='{node.attributes[attr]}']"
|
|
122
|
-
|
|
123
|
-
return hints
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
def convert_selector_map_for_llm(selector_map) -> dict:
|
|
127
|
-
"""
|
|
128
|
-
Convert complex selector_map to simplified format suitable for LLM understanding and JS code writing
|
|
129
|
-
"""
|
|
130
|
-
simplified_elements = []
|
|
131
|
-
|
|
132
|
-
for element_index, node in selector_map.items():
|
|
133
|
-
if node.is_visible and node.element_index is not None: # Only include visible interactive elements
|
|
134
|
-
element_info = {
|
|
135
|
-
"tag": node.tag_name,
|
|
136
|
-
"text": node.get_meaningful_text_for_llm()[:200], # Limit text length
|
|
137
|
-
|
|
138
|
-
# Selector information - most needed for JS code
|
|
139
|
-
"selectors": {
|
|
140
|
-
"xpath": node.xpath,
|
|
141
|
-
"css_hints": extract_css_hints(node), # Extract id, class etc
|
|
142
|
-
},
|
|
143
|
-
|
|
144
|
-
# Element semantics
|
|
145
|
-
"role": node.ax_node.role if node.ax_node else None,
|
|
146
|
-
"type": node.attributes.get("type"),
|
|
147
|
-
"aria_label": node.attributes.get("aria-label"),
|
|
148
|
-
|
|
149
|
-
# Key attributes
|
|
150
|
-
"attributes": {k: v for k, v in node.attributes.items()
|
|
151
|
-
if k in ["id", "class", "name", "href", "src", "value", "placeholder", "data-testid"]},
|
|
152
|
-
|
|
153
|
-
# Interactivity
|
|
154
|
-
"is_clickable": node.snapshot_node.is_clickable if node.snapshot_node else False,
|
|
155
|
-
"is_input": node.tag_name.lower() in ["input", "textarea", "select"],
|
|
156
|
-
|
|
157
|
-
# Structure information
|
|
158
|
-
"parent_tag": node.parent_node.tag_name if node.parent_node else None,
|
|
159
|
-
"position_info": f"{node.tag_name}[{get_sibling_position(node)}]"
|
|
160
|
-
}
|
|
161
|
-
simplified_elements.append(element_info)
|
|
162
|
-
|
|
163
|
-
return {
|
|
164
|
-
"page_elements": simplified_elements,
|
|
165
|
-
"total_elements": len(simplified_elements)
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
|
|
169
53
|
class VibeSurfTools:
|
|
170
54
|
def __init__(self, exclude_actions: list[str] = [], mcp_server_config: Optional[Dict[str, Any]] = None,
|
|
171
55
|
composio_client: ComposioClient = None):
|
|
@@ -182,7 +66,7 @@ class VibeSurfTools:
|
|
|
182
66
|
|
|
183
67
|
def _register_skills(self):
|
|
184
68
|
@self.registry.action(
|
|
185
|
-
'
|
|
69
|
+
'parallel search',
|
|
186
70
|
param_model=SkillSearchAction,
|
|
187
71
|
)
|
|
188
72
|
async def skill_search(
|
|
@@ -373,7 +257,7 @@ Format: [index1, index2, index3, ...]
|
|
|
373
257
|
await browser_manager.unregister_agent(agent_id, close_tabs=True)
|
|
374
258
|
|
|
375
259
|
@self.registry.action(
|
|
376
|
-
'
|
|
260
|
+
'',
|
|
377
261
|
param_model=SkillCrawlAction,
|
|
378
262
|
)
|
|
379
263
|
async def skill_crawl(
|
|
@@ -393,13 +277,19 @@ Format: [index1, index2, index3, ...]
|
|
|
393
277
|
browser_session = browser_manager.main_browser_session
|
|
394
278
|
|
|
395
279
|
# If tab_id is provided, switch to that tab
|
|
280
|
+
target_id = None
|
|
396
281
|
if params.tab_id:
|
|
397
282
|
target_id = await browser_session.get_target_id_from_tab_id(params.tab_id)
|
|
283
|
+
current_target_id = None
|
|
284
|
+
if browser_session.agent_focus:
|
|
285
|
+
current_target_id = browser_session.agent_focus.target_id
|
|
286
|
+
if current_target_id != target_id:
|
|
287
|
+
browser_session._dom_watchdog.enhanced_dom_tree = None
|
|
398
288
|
await browser_session.get_or_create_cdp_session(target_id, focus=True)
|
|
399
289
|
|
|
400
290
|
# Extract structured content using the existing method
|
|
401
291
|
extracted_content = await self._extract_structured_content(
|
|
402
|
-
browser_session, params.query, llm
|
|
292
|
+
browser_session, params.query, llm, target_id=target_id
|
|
403
293
|
)
|
|
404
294
|
|
|
405
295
|
current_url = await browser_session.get_current_page_url()
|
|
@@ -426,7 +316,7 @@ Format: [index1, index2, index3, ...]
|
|
|
426
316
|
return ActionResult(error=f'Skill crawl failed: {str(e)}')
|
|
427
317
|
|
|
428
318
|
@self.registry.action(
|
|
429
|
-
'
|
|
319
|
+
'',
|
|
430
320
|
param_model=SkillSummaryAction,
|
|
431
321
|
)
|
|
432
322
|
async def skill_summary(
|
|
@@ -446,13 +336,19 @@ Format: [index1, index2, index3, ...]
|
|
|
446
336
|
browser_session = browser_manager.main_browser_session
|
|
447
337
|
|
|
448
338
|
# If tab_id is provided, switch to that tab
|
|
339
|
+
target_id = None
|
|
449
340
|
if params.tab_id:
|
|
450
341
|
target_id = await browser_session.get_target_id_from_tab_id(params.tab_id)
|
|
342
|
+
current_target_id = None
|
|
343
|
+
if browser_session.agent_focus:
|
|
344
|
+
current_target_id = browser_session.agent_focus.target_id
|
|
345
|
+
if current_target_id != target_id:
|
|
346
|
+
browser_session._dom_watchdog.enhanced_dom_tree = None
|
|
451
347
|
await browser_session.get_or_create_cdp_session(target_id, focus=True)
|
|
452
348
|
|
|
453
349
|
# Extract and summarize content
|
|
454
350
|
summary = await self._extract_structured_content(
|
|
455
|
-
browser_session, "Provide a comprehensive summary of this webpage", llm
|
|
351
|
+
browser_session, "Provide a comprehensive summary of this webpage", llm, target_id=target_id
|
|
456
352
|
)
|
|
457
353
|
|
|
458
354
|
current_url = await browser_session.get_current_page_url()
|
|
@@ -479,7 +375,7 @@ Format: [index1, index2, index3, ...]
|
|
|
479
375
|
return ActionResult(error=f'Skill summary failed: {str(e)}')
|
|
480
376
|
|
|
481
377
|
@self.registry.action(
|
|
482
|
-
'
|
|
378
|
+
'',
|
|
483
379
|
param_model=SkillTakeScreenshotAction,
|
|
484
380
|
)
|
|
485
381
|
async def skill_screenshot(
|
|
@@ -500,7 +396,7 @@ Format: [index1, index2, index3, ...]
|
|
|
500
396
|
await browser_session.get_or_create_cdp_session(target_id, focus=True)
|
|
501
397
|
|
|
502
398
|
# Take screenshot using browser session
|
|
503
|
-
|
|
399
|
+
screenshot_bytes = await browser_session.take_screenshot()
|
|
504
400
|
|
|
505
401
|
# Generate timestamp for filename
|
|
506
402
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
@@ -520,7 +416,7 @@ Format: [index1, index2, index3, ...]
|
|
|
520
416
|
filepath = screenshots_dir / filename
|
|
521
417
|
|
|
522
418
|
with open(filepath, "wb") as f:
|
|
523
|
-
f.write(
|
|
419
|
+
f.write(screenshot_bytes)
|
|
524
420
|
|
|
525
421
|
msg = f'📸 Screenshot saved to path: [{filename}]({str(filepath.relative_to(fs_dir))})'
|
|
526
422
|
logger.info(msg)
|
|
@@ -536,7 +432,7 @@ Format: [index1, index2, index3, ...]
|
|
|
536
432
|
return ActionResult(error=error_msg)
|
|
537
433
|
|
|
538
434
|
@self.registry.action(
|
|
539
|
-
'
|
|
435
|
+
'Execute JavaScript code on webpage',
|
|
540
436
|
param_model=SkillCodeAction,
|
|
541
437
|
)
|
|
542
438
|
async def skill_code(
|
|
@@ -643,7 +539,7 @@ Return ONLY the JavaScript code, no explanations or markdown formatting."""
|
|
|
643
539
|
|
|
644
540
|
USER REQUIREMENT: {params.code_requirement}
|
|
645
541
|
|
|
646
|
-
Web Page
|
|
542
|
+
Web Page Content:
|
|
647
543
|
{web_page_html}
|
|
648
544
|
|
|
649
545
|
Generate JavaScript code to fulfill the requirement:"""
|
|
@@ -815,7 +711,7 @@ Please generate alternative JavaScript code that avoids this system error:"""
|
|
|
815
711
|
return ActionResult(error=f'Skill code failed: {str(e)}')
|
|
816
712
|
|
|
817
713
|
@self.registry.action(
|
|
818
|
-
'
|
|
714
|
+
'Return the guideline for deep research. Please follow the guideline to do real deep research actions.',
|
|
819
715
|
param_model=NoParamsAction,
|
|
820
716
|
)
|
|
821
717
|
async def skill_deep_research(
|
|
@@ -866,7 +762,7 @@ Please generate alternative JavaScript code that avoids this system error:"""
|
|
|
866
762
|
)
|
|
867
763
|
|
|
868
764
|
@self.registry.action(
|
|
869
|
-
'
|
|
765
|
+
'Get comprehensive financial data for stocks - retrieve company information, historical prices, news, earnings, dividends, analyst recommendations and other financial data using Yahoo Finance.',
|
|
870
766
|
param_model=SkillFinanceAction,
|
|
871
767
|
)
|
|
872
768
|
async def skill_finance(
|
|
@@ -951,7 +847,7 @@ Please generate alternative JavaScript code that avoids this system error:"""
|
|
|
951
847
|
return ActionResult(error=error_msg, extracted_content=error_msg)
|
|
952
848
|
|
|
953
849
|
@self.registry.action(
|
|
954
|
-
'
|
|
850
|
+
'',
|
|
955
851
|
param_model=SkillXhsAction,
|
|
956
852
|
)
|
|
957
853
|
async def skill_xhs(
|
|
@@ -1055,7 +951,7 @@ Please generate alternative JavaScript code that avoids this system error:"""
|
|
|
1055
951
|
return ActionResult(error=error_msg, extracted_content=error_msg)
|
|
1056
952
|
|
|
1057
953
|
@self.registry.action(
|
|
1058
|
-
'
|
|
954
|
+
'',
|
|
1059
955
|
param_model=SkillWeiboAction,
|
|
1060
956
|
)
|
|
1061
957
|
async def skill_weibo(
|
|
@@ -1163,7 +1059,7 @@ Please generate alternative JavaScript code that avoids this system error:"""
|
|
|
1163
1059
|
return ActionResult(error=error_msg, extracted_content=error_msg)
|
|
1164
1060
|
|
|
1165
1061
|
@self.registry.action(
|
|
1166
|
-
'
|
|
1062
|
+
'',
|
|
1167
1063
|
param_model=SkillDouyinAction,
|
|
1168
1064
|
)
|
|
1169
1065
|
async def skill_douyin(
|
|
@@ -1264,18 +1160,7 @@ Please generate alternative JavaScript code that avoids this system error:"""
|
|
|
1264
1160
|
return ActionResult(error=error_msg, extracted_content=error_msg)
|
|
1265
1161
|
|
|
1266
1162
|
@self.registry.action(
|
|
1267
|
-
"""
|
|
1268
|
-
Methods:
|
|
1269
|
-
search_videos,
|
|
1270
|
-
get_video_details,
|
|
1271
|
-
get_video_comments,
|
|
1272
|
-
get_channel_info,
|
|
1273
|
-
get_channel_videos,
|
|
1274
|
-
get_trending_videos,
|
|
1275
|
-
get_video_transcript.
|
|
1276
|
-
|
|
1277
|
-
If users want to know the specific content of this video, please use get_video_transcript to get detailed video content first.
|
|
1278
|
-
""",
|
|
1163
|
+
"""YouTube API - If users want to know the specific content of this video, please use get_video_transcript to get detailed video content first.""",
|
|
1279
1164
|
param_model=SkillYoutubeAction,
|
|
1280
1165
|
)
|
|
1281
1166
|
async def skill_youtube(
|
|
@@ -1641,15 +1526,20 @@ Return results as a JSON array: [{{"title": "...", "url": "...", "summary": "...
|
|
|
1641
1526
|
deduplicated.sort(key=relevance_score, reverse=True)
|
|
1642
1527
|
return deduplicated
|
|
1643
1528
|
|
|
1644
|
-
async def _extract_structured_content(self, browser_session, query: str, llm: BaseChatModel
|
|
1529
|
+
async def _extract_structured_content(self, browser_session, query: str, llm: BaseChatModel,
|
|
1530
|
+
target_id: str | None = None, extract_links: bool = False):
|
|
1645
1531
|
"""Helper method to extract structured content from current page"""
|
|
1646
1532
|
MAX_CHAR_LIMIT = 30000
|
|
1647
1533
|
|
|
1648
1534
|
# Extract clean markdown using the existing method
|
|
1649
1535
|
try:
|
|
1650
|
-
|
|
1536
|
+
from browser_use.dom.markdown_extractor import extract_clean_markdown
|
|
1537
|
+
|
|
1538
|
+
content, content_stats = await extract_clean_markdown(
|
|
1539
|
+
browser_session=browser_session, extract_links=extract_links
|
|
1540
|
+
)
|
|
1651
1541
|
except Exception as e:
|
|
1652
|
-
raise RuntimeError(f'Could not extract clean markdown: {
|
|
1542
|
+
raise RuntimeError(f'Could not extract clean markdown: {e}')
|
|
1653
1543
|
|
|
1654
1544
|
# Smart truncation with context preservation
|
|
1655
1545
|
if len(content) > MAX_CHAR_LIMIT:
|
|
@@ -1697,90 +1587,9 @@ You will be given a query and the markdown of a webpage that has been filtered t
|
|
|
1697
1587
|
logger.debug(f'Error extracting content: {e}')
|
|
1698
1588
|
raise RuntimeError(str(e))
|
|
1699
1589
|
|
|
1700
|
-
async def extract_clean_markdown(
|
|
1701
|
-
self, browser_session: BrowserSession, extract_links: bool = True
|
|
1702
|
-
) -> tuple[str, dict[str, Any]]:
|
|
1703
|
-
"""Extract clean markdown from the current page."""
|
|
1704
|
-
import re
|
|
1705
|
-
|
|
1706
|
-
# Get HTML content from current page
|
|
1707
|
-
cdp_session = await browser_session.get_or_create_cdp_session()
|
|
1708
|
-
try:
|
|
1709
|
-
body_id = await cdp_session.cdp_client.send.DOM.getDocument(session_id=cdp_session.session_id)
|
|
1710
|
-
page_html_result = await cdp_session.cdp_client.send.DOM.getOuterHTML(
|
|
1711
|
-
params={'backendNodeId': body_id['root']['backendNodeId']}, session_id=cdp_session.session_id
|
|
1712
|
-
)
|
|
1713
|
-
page_html = page_html_result['outerHTML']
|
|
1714
|
-
current_url = await browser_session.get_current_page_url()
|
|
1715
|
-
except Exception as e:
|
|
1716
|
-
raise RuntimeError(f"Couldn't extract page content: {e}")
|
|
1717
|
-
|
|
1718
|
-
original_html_length = len(page_html)
|
|
1719
|
-
|
|
1720
|
-
# Use html2text for clean markdown conversion
|
|
1721
|
-
import html2text
|
|
1722
|
-
|
|
1723
|
-
h = html2text.HTML2Text()
|
|
1724
|
-
h.ignore_links = not extract_links
|
|
1725
|
-
h.ignore_images = True
|
|
1726
|
-
h.ignore_emphasis = False
|
|
1727
|
-
h.body_width = 0 # Don't wrap lines
|
|
1728
|
-
h.unicode_snob = True
|
|
1729
|
-
h.skip_internal_links = True
|
|
1730
|
-
content = h.handle(page_html)
|
|
1731
|
-
|
|
1732
|
-
initial_markdown_length = len(content)
|
|
1733
|
-
|
|
1734
|
-
# Minimal cleanup - html2text already does most of the work
|
|
1735
|
-
content = re.sub(r'%[0-9A-Fa-f]{2}', '', content) # Remove any remaining URL encoding
|
|
1736
|
-
|
|
1737
|
-
# Apply light preprocessing to clean up excessive whitespace
|
|
1738
|
-
content, chars_filtered = self._preprocess_markdown_content(content)
|
|
1739
|
-
|
|
1740
|
-
final_filtered_length = len(content)
|
|
1741
|
-
|
|
1742
|
-
# Content statistics
|
|
1743
|
-
stats = {
|
|
1744
|
-
'url': current_url,
|
|
1745
|
-
'original_html_chars': original_html_length,
|
|
1746
|
-
'initial_markdown_chars': initial_markdown_length,
|
|
1747
|
-
'filtered_chars_removed': chars_filtered,
|
|
1748
|
-
'final_filtered_chars': final_filtered_length,
|
|
1749
|
-
}
|
|
1750
|
-
|
|
1751
|
-
return content, stats
|
|
1752
|
-
|
|
1753
|
-
def _preprocess_markdown_content(self, content: str, max_newlines: int = 3) -> tuple[str, int]:
|
|
1754
|
-
"""Light preprocessing of html2text output - minimal cleanup since html2text is already clean."""
|
|
1755
|
-
import re
|
|
1756
|
-
|
|
1757
|
-
original_length = len(content)
|
|
1758
|
-
|
|
1759
|
-
# Compress consecutive newlines (4+ newlines become max_newlines)
|
|
1760
|
-
content = re.sub(r'\n{4,}', '\n' * max_newlines, content)
|
|
1761
|
-
|
|
1762
|
-
# Remove lines that are only whitespace or very short (likely artifacts)
|
|
1763
|
-
lines = content.split('\n')
|
|
1764
|
-
filtered_lines = []
|
|
1765
|
-
for line in lines:
|
|
1766
|
-
stripped = line.strip()
|
|
1767
|
-
# Keep lines with substantial content (html2text output is already clean)
|
|
1768
|
-
if len(stripped) > 2:
|
|
1769
|
-
filtered_lines.append(line)
|
|
1770
|
-
|
|
1771
|
-
content = '\n'.join(filtered_lines)
|
|
1772
|
-
content = content.strip()
|
|
1773
|
-
|
|
1774
|
-
chars_filtered = original_length - len(content)
|
|
1775
|
-
return content, chars_filtered
|
|
1776
|
-
|
|
1777
1590
|
def _register_browser_use_agent(self):
|
|
1778
1591
|
@self.registry.action(
|
|
1779
|
-
'Execute browser_use agent tasks.
|
|
1780
|
-
'parallel execution of multiple tasks for improved efficiency. '
|
|
1781
|
-
'Accepts a list of tasks where each task can specify a tab_id (optional), '
|
|
1782
|
-
'task description (focusing on goals and expected returns), and task_files (optional). '
|
|
1783
|
-
'Browser_use agent has strong planning and execution capabilities, only needs task descriptions and desired outcomes.',
|
|
1592
|
+
'Execute browser_use agent tasks.',
|
|
1784
1593
|
param_model=BrowserUseAgentExecution,
|
|
1785
1594
|
)
|
|
1786
1595
|
async def execute_browser_use_agent(
|
|
@@ -1804,8 +1613,7 @@ You will be given a query and the markdown of a webpage that has been filtered t
|
|
|
1804
1613
|
|
|
1805
1614
|
def _register_report_writer_agent(self):
|
|
1806
1615
|
@self.registry.action(
|
|
1807
|
-
'Execute report writer agent to generate HTML reports. '
|
|
1808
|
-
'Task should describe report requirements, goals, insights observed, and any hints or tips for generating the report.',
|
|
1616
|
+
'Execute report writer agent to generate HTML reports. ',
|
|
1809
1617
|
param_model=ReportWriterTask,
|
|
1810
1618
|
)
|
|
1811
1619
|
async def execute_report_writer_agent(
|
|
@@ -2004,7 +1812,7 @@ You will be given a query and the markdown of a webpage that has been filtered t
|
|
|
2004
1812
|
|
|
2005
1813
|
def _register_file_actions(self):
|
|
2006
1814
|
@self.registry.action(
|
|
2007
|
-
'
|
|
1815
|
+
''
|
|
2008
1816
|
)
|
|
2009
1817
|
async def replace_file_str(file_name: str, old_str: str, new_str: str, file_system: CustomFileSystem):
|
|
2010
1818
|
result = await file_system.replace_file_str(file_name, old_str, new_str)
|
|
@@ -2149,7 +1957,7 @@ You will be given a query and the markdown of a webpage that has been filtered t
|
|
|
2149
1957
|
raise RuntimeError(str(e))
|
|
2150
1958
|
|
|
2151
1959
|
@self.registry.action(
|
|
2152
|
-
'
|
|
1960
|
+
''
|
|
2153
1961
|
)
|
|
2154
1962
|
async def write_file(
|
|
2155
1963
|
file_path: str,
|
|
@@ -2171,7 +1979,7 @@ You will be given a query and the markdown of a webpage that has been filtered t
|
|
|
2171
1979
|
return ActionResult(extracted_content=result, long_term_memory=result)
|
|
2172
1980
|
|
|
2173
1981
|
@self.registry.action(
|
|
2174
|
-
'
|
|
1982
|
+
'Set external_src=True to copy from external file(absolute path)to FileSystem, False to copy within FileSystem.'
|
|
2175
1983
|
)
|
|
2176
1984
|
async def copy_file(src_file_path: str, dst_file_path: str, file_system: CustomFileSystem,
|
|
2177
1985
|
external_src: bool = False):
|
|
@@ -2228,7 +2036,7 @@ You will be given a query and the markdown of a webpage that has been filtered t
|
|
|
2228
2036
|
)
|
|
2229
2037
|
|
|
2230
2038
|
@self.registry.action(
|
|
2231
|
-
'List
|
|
2039
|
+
'List a directory within the FileSystem. Use empty string "" or "." to list the root FileSystem, or provide relative path for subdirectory.'
|
|
2232
2040
|
)
|
|
2233
2041
|
async def list_directory(directory_path: str, file_system: CustomFileSystem):
|
|
2234
2042
|
result = await file_system.list_directory(directory_path)
|
|
@@ -2240,7 +2048,7 @@ You will be given a query and the markdown of a webpage that has been filtered t
|
|
|
2240
2048
|
)
|
|
2241
2049
|
|
|
2242
2050
|
@self.registry.action(
|
|
2243
|
-
'
|
|
2051
|
+
'search for query or keywords and return surrounding context',
|
|
2244
2052
|
param_model=GrepContentAction,
|
|
2245
2053
|
)
|
|
2246
2054
|
async def grep_content_from_file(
|
vibe_surf/tools/views.py
CHANGED
|
@@ -56,7 +56,7 @@ class BrowserUseAgentExecution(BaseModel):
|
|
|
56
56
|
"""Parameters for executing browser_use agent tasks in parallel"""
|
|
57
57
|
tasks: list[BrowserUseAgentTask] = Field(
|
|
58
58
|
description='List of tasks to execute concurrently using browser_use agents for improved efficiency. '
|
|
59
|
-
'If only one task is provided, the agent can take over the entire browser and can also see and operate all tabs.',
|
|
59
|
+
'If only one task and no tab_id is provided, the agent can take over the entire browser and can also see and operate all tabs.',
|
|
60
60
|
min_length=1,
|
|
61
61
|
)
|
|
62
62
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: vibesurf
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.37
|
|
4
4
|
Summary: VibeSurf: A powerful browser assistant for vibe surfing
|
|
5
5
|
Author: WarmShao
|
|
6
6
|
License: Apache-2.0
|
|
@@ -37,7 +37,6 @@ Requires-Dist: aiosqlite>=0.21.0
|
|
|
37
37
|
Requires-Dist: rich>=13.0.0
|
|
38
38
|
Requires-Dist: greenlet>=3.2.4
|
|
39
39
|
Requires-Dist: getmac>=0.9.5
|
|
40
|
-
Requires-Dist: browser-use==0.7.10
|
|
41
40
|
Requires-Dist: markdown-pdf>=1.9
|
|
42
41
|
Requires-Dist: nanoid>=2.0.0
|
|
43
42
|
Requires-Dist: markdownify>=1.2.0
|
|
@@ -48,6 +47,12 @@ Requires-Dist: pyexecjs>=1.5.1
|
|
|
48
47
|
Requires-Dist: youtube-transcript-api>=1.2.2
|
|
49
48
|
Requires-Dist: composio>=0.8.20
|
|
50
49
|
Requires-Dist: composio-langchain>=0.8.20
|
|
50
|
+
Requires-Dist: browser-use==0.8.1
|
|
51
|
+
Requires-Dist: google-genai<2.0.0,>=1.29.0
|
|
52
|
+
Requires-Dist: openai<2.0.0,>=1.99.2
|
|
53
|
+
Requires-Dist: google-api-python-client>=2.174.0
|
|
54
|
+
Requires-Dist: google-auth>=2.40.3
|
|
55
|
+
Requires-Dist: google-auth-oauthlib>=1.2.2
|
|
51
56
|
Dynamic: license-file
|
|
52
57
|
|
|
53
58
|
# VibeSurf: A powerful browser assistant for vibe surfing
|
|
@@ -103,6 +108,11 @@ Start the VibeSurf browser assistant
|
|
|
103
108
|
uv run vibesurf
|
|
104
109
|
```
|
|
105
110
|
|
|
111
|
+
### 4. Start to Use
|
|
112
|
+
|
|
113
|
+
<video src="https://github.com/user-attachments/assets/86dba2e4-3f33-4ccf-b400-d07cf1a481a0" controls="controls">Your browser does not support playing this video!</video>
|
|
114
|
+
|
|
115
|
+
|
|
106
116
|
## 👩💻 For Contributors
|
|
107
117
|
|
|
108
118
|
Want to contribute to VibeSurf? Follow these steps to set up your development environment:
|
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
vibe_surf/__init__.py,sha256=WtduuMFGauMD_9dpk4fnRnLTAP6ka9Lfu0feAFNzLfo,339
|
|
2
|
-
vibe_surf/_version.py,sha256=
|
|
3
|
-
vibe_surf/cli.py,sha256=
|
|
2
|
+
vibe_surf/_version.py,sha256=Un30RAtqEXXKI1lbcmQdjd4zLY640yIy_ojmAQvwoug,706
|
|
3
|
+
vibe_surf/cli.py,sha256=pzJs--bjV6ZU7njJnxK_lh76l3pQ3opg_5V9Hw17PQw,21924
|
|
4
4
|
vibe_surf/common.py,sha256=_WWMxen5wFwzUjEShn3yDVC1OBFUiJ6Vccadi6tuG6w,1215
|
|
5
5
|
vibe_surf/logger.py,sha256=k53MFA96QX6t9OfcOf1Zws8PP0OOqjVJfhUD3Do9lKw,3043
|
|
6
6
|
vibe_surf/utils.py,sha256=3j6uRkgjOAIDYmbpW8C-DpYhdf1Kvahz715KSriVIk0,261
|
|
7
7
|
vibe_surf/agents/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
-
vibe_surf/agents/browser_use_agent.py,sha256=
|
|
9
|
-
vibe_surf/agents/report_writer_agent.py,sha256=
|
|
10
|
-
vibe_surf/agents/vibe_surf_agent.py,sha256=
|
|
8
|
+
vibe_surf/agents/browser_use_agent.py,sha256=Uj9tbCR5_dbrSSnKuajbL4zP_E9CwLMrdvw0EmDwnC0,34473
|
|
9
|
+
vibe_surf/agents/report_writer_agent.py,sha256=0MqRFHQpL7-HIDJt8r3TUqI0qh9SMb3LlkZSWc3d0Sg,23903
|
|
10
|
+
vibe_surf/agents/vibe_surf_agent.py,sha256=y9qD72wBpx9q2Vk2nSP07t1L8cycpJ0zh6EB11-8cb0,78793
|
|
11
11
|
vibe_surf/agents/views.py,sha256=yHjNJloa-aofVTGyuRy08tBYP_Y3XLqt1DUWOUmHRng,4825
|
|
12
12
|
vibe_surf/agents/prompts/__init__.py,sha256=l4ieA0D8kLJthyNN85FKLNe4ExBa3stY3l-aImLDRD0,36
|
|
13
13
|
vibe_surf/agents/prompts/report_writer_prompt.py,sha256=sZE8MUT1CDLmRzbnbEQzAvTwJjpITgh2Q8g1_eXmkzE,4454
|
|
14
14
|
vibe_surf/agents/prompts/vibe_surf_prompt.py,sha256=UnZxgy9sgr4u1RNYr6VJBk3sEUbFasu2z75QGsILXk8,7507
|
|
15
15
|
vibe_surf/backend/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
|
-
vibe_surf/backend/llm_config.py,sha256=
|
|
16
|
+
vibe_surf/backend/llm_config.py,sha256=buzNzncyz_Lu__mRU7aMtCpfKmWjbgpneBzcz_cH9Jg,5697
|
|
17
17
|
vibe_surf/backend/main.py,sha256=8_n9dUF972E_UqguXK4zfq5AeulTUeey4X60qOXnigY,8272
|
|
18
|
-
vibe_surf/backend/shared_state.py,sha256=
|
|
18
|
+
vibe_surf/backend/shared_state.py,sha256=AK7MGBZMbaCxhuBgnfRtfcxMQS9SACh75W6UM8w4vMA,28654
|
|
19
19
|
vibe_surf/backend/voice_model_config.py,sha256=oee4fvOexXKzKRDv2-FEKQj7Z2OznACrj6mfWRGy7h0,567
|
|
20
20
|
vibe_surf/backend/api/__init__.py,sha256=XxF1jUOORpLYCfFuPrrnUGRnOrr6ClH0_MNPU-4RnSs,68
|
|
21
21
|
vibe_surf/backend/api/activity.py,sha256=_cnHusqolt5Hf3KdAf6FK-3sBc-TSaadmb5dJxGI57A,9398
|
|
@@ -39,17 +39,17 @@ vibe_surf/backend/database/migrations/v004_add_voice_profiles.sql,sha256=-9arjQB
|
|
|
39
39
|
vibe_surf/backend/database/migrations/v005_add_composio_integration.sql,sha256=QuTrRYyOmHdepRcHHwrGlLpsNq9qo5mnOB5LfXtN-CU,1172
|
|
40
40
|
vibe_surf/backend/database/migrations/v006_add_credentials_table.sql,sha256=udyVO_L8CvdBKGNfmnxj-kU7UxR9EzUqMpHuV1h46vU,812
|
|
41
41
|
vibe_surf/backend/utils/__init__.py,sha256=V8leMFp7apAglUAoCHPZrNNcRHthSLYIudIJE5qwjb0,184
|
|
42
|
-
vibe_surf/backend/utils/encryption.py,sha256=
|
|
43
|
-
vibe_surf/backend/utils/llm_factory.py,sha256=
|
|
42
|
+
vibe_surf/backend/utils/encryption.py,sha256=LYTKJdOrKfQt8U7GUXIEsns6iBeXnZ7jlOPOgxvkD5c,5112
|
|
43
|
+
vibe_surf/backend/utils/llm_factory.py,sha256=KKxi3yi61OU6Prd3druzYkD7JQkqwArkzALWYYb8G3g,9570
|
|
44
44
|
vibe_surf/backend/utils/utils.py,sha256=nY5-DGY4MfPumpQxXLQiodO0Db7lChryWIHbY9h971o,1137
|
|
45
45
|
vibe_surf/browser/__init__.py,sha256=_UToO2fZfSCrfjOcxhn4Qq7ZLbYeyPuUUEmqIva-Yv8,325
|
|
46
|
-
vibe_surf/browser/agen_browser_profile.py,sha256=
|
|
47
|
-
vibe_surf/browser/agent_browser_session.py,sha256=
|
|
46
|
+
vibe_surf/browser/agen_browser_profile.py,sha256=fHk5q6hkwHfoNIuUitUBPJLdqbsZ7xjsLU6vTTlXEQI,6177
|
|
47
|
+
vibe_surf/browser/agent_browser_session.py,sha256=Bzak1psvyqvQ-fmvXP0Dky-J6gfcaL4NyQEN_vrXsKc,38977
|
|
48
48
|
vibe_surf/browser/browser_manager.py,sha256=PFJ9flmqR2uokuRZ3PDh_Dy6_6qcQ6kH_eRc1yT6Iq8,11257
|
|
49
49
|
vibe_surf/browser/utils.py,sha256=bBtpMiyz2ixWOr31GbJwZ8pVYcnxztKjTJJO92z1BDY,35742
|
|
50
50
|
vibe_surf/browser/watchdogs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
51
|
-
vibe_surf/browser/watchdogs/action_watchdog.py,sha256=
|
|
52
|
-
vibe_surf/browser/watchdogs/dom_watchdog.py,sha256=
|
|
51
|
+
vibe_surf/browser/watchdogs/action_watchdog.py,sha256=6J7ox8hlA3EeRI6YiBxpKCdaybe7n_zp9UpdBWSTc4s,609
|
|
52
|
+
vibe_surf/browser/watchdogs/dom_watchdog.py,sha256=9fNB-FjqJbeKpKp_UezPeymI-0yTUu_0LB3SEHXfdr8,11023
|
|
53
53
|
vibe_surf/chrome_extension/background.js,sha256=9YLbLmBl__PPUwN6Edf0EQfE53Br1VfjlUiIkznf4n4,30181
|
|
54
54
|
vibe_surf/chrome_extension/config.js,sha256=rd4I2pyEWEYgVKIj7wEcwd8ouotm7ZlGwsRRSWMmAGY,1311
|
|
55
55
|
vibe_surf/chrome_extension/content.js,sha256=cB67jK3vIE5zrpXAfi3p50H3EyTqK5xockOph0Q4kQg,13708
|
|
@@ -91,20 +91,21 @@ vibe_surf/chrome_extension/styles/settings-responsive.css,sha256=jIlYWkOpom0iTWQ
|
|
|
91
91
|
vibe_surf/chrome_extension/styles/settings-utilities.css,sha256=3PuQS2857kg83d5erLbLdo_7J95-qV-qyNWS5M-w1oQ,505
|
|
92
92
|
vibe_surf/chrome_extension/styles/variables.css,sha256=enjyhsa0PeU3b-3uiXa-VkV-1-h2-Ai3m4KpmC2k0rY,2984
|
|
93
93
|
vibe_surf/llm/__init__.py,sha256=_vDVPo6STf343p1SgMQrF5023hicAx0g83pK2Gbk4Ek,601
|
|
94
|
-
vibe_surf/llm/openai_compatible.py,sha256=
|
|
94
|
+
vibe_surf/llm/openai_compatible.py,sha256=Ftir3mvzJxi2dPfQj1fcpkqm0180Xm1HNAhEuUk-V5I,15926
|
|
95
95
|
vibe_surf/telemetry/__init__.py,sha256=izfMX5F3ff92t4E6Y-6onYG8oJAx-bGtFFXRymPwFp0,2085
|
|
96
96
|
vibe_surf/telemetry/service.py,sha256=7AsvouGUI5lz5hsfxitVcarCwbfmGJHjJkslDZiTEhY,3717
|
|
97
|
-
vibe_surf/telemetry/views.py,sha256=
|
|
97
|
+
vibe_surf/telemetry/views.py,sha256=llbXm4GkYLlJc5vYKDDUqcKDCUOJ_ScS22rwzxdSUEc,4602
|
|
98
98
|
vibe_surf/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
99
|
-
vibe_surf/tools/browser_use_tools.py,sha256=
|
|
99
|
+
vibe_surf/tools/browser_use_tools.py,sha256=Al65_YfOoMk112oeDz1gRgHC08RsaL19DSFr3oG0vCw,30726
|
|
100
100
|
vibe_surf/tools/composio_client.py,sha256=hbvUYiTSlNNRvBQmoVkxtJnd6pXm7PaevcTo1nQZEx8,18822
|
|
101
|
-
vibe_surf/tools/file_system.py,sha256=
|
|
101
|
+
vibe_surf/tools/file_system.py,sha256=wzEWQJUVLE7xLSOIz4Fx7MWkniQaSJhgCGQAZ9eMJ38,19374
|
|
102
102
|
vibe_surf/tools/finance_tools.py,sha256=E8rmblp57e_cp0tFbdZ7BY3_upNlk4Whk0bYc_SFCJE,27284
|
|
103
103
|
vibe_surf/tools/mcp_client.py,sha256=ZrwzboDBf7-rtzO8JYpBhvKGg88CMfbrXlhtIs8pXSQ,3167
|
|
104
104
|
vibe_surf/tools/report_writer_tools.py,sha256=2CyTTXOahTKZo7XwyWDDhJ--1mRA0uTtUWxu_DACAY0,776
|
|
105
|
+
vibe_surf/tools/utils.py,sha256=-Hu60Qu0M_gJpU7Ow8WpgBbZzMv_76DsYYj7eFk6M08,4219
|
|
105
106
|
vibe_surf/tools/vibesurf_registry.py,sha256=Z-8d9BrJl3RFMEK0Tw1Q5xNHX2kZGsnIGCTBZ3RM-pw,2159
|
|
106
|
-
vibe_surf/tools/vibesurf_tools.py,sha256=
|
|
107
|
-
vibe_surf/tools/views.py,sha256=
|
|
107
|
+
vibe_surf/tools/vibesurf_tools.py,sha256=feBejeVXacbfWjzPsKVqc2dnYVoI-jalAtwdXB6_N4U,113152
|
|
108
|
+
vibe_surf/tools/views.py,sha256=r_xHyBHR-d8cSgvZ-GJVnB4k7pQq9g5gsp882zM70mM,12897
|
|
108
109
|
vibe_surf/tools/voice_asr.py,sha256=AJG0yq_Jq-j8ulDlbPhVFfK1jch9_ASesis73iki9II,4702
|
|
109
110
|
vibe_surf/tools/website_api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
110
111
|
vibe_surf/tools/website_api/douyin/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -119,9 +120,9 @@ vibe_surf/tools/website_api/xhs/helpers.py,sha256=Dq2RyYKClBQ2ha2yEfpS1mtZswx0z9
|
|
|
119
120
|
vibe_surf/tools/website_api/youtube/__init__.py,sha256=QWmZWSqo1O6XtaWP-SuL3HrBLYINjEWEyOy-KCytGDw,1145
|
|
120
121
|
vibe_surf/tools/website_api/youtube/client.py,sha256=0kKy7fkBNc63hRvDgXaKnguDpDJ92rG8T2nT_Y1F5MQ,51989
|
|
121
122
|
vibe_surf/tools/website_api/youtube/helpers.py,sha256=GPgqfNirLYjIpk1OObvoXd2Ktq-ahKOOKHO2WwQVXCw,12931
|
|
122
|
-
vibesurf-0.1.
|
|
123
|
-
vibesurf-0.1.
|
|
124
|
-
vibesurf-0.1.
|
|
125
|
-
vibesurf-0.1.
|
|
126
|
-
vibesurf-0.1.
|
|
127
|
-
vibesurf-0.1.
|
|
123
|
+
vibesurf-0.1.37.dist-info/licenses/LICENSE,sha256=vRmTjOYvD8RLiSGYYmFHnveYNswtO1uvSk1sd-Eu7sg,2037
|
|
124
|
+
vibesurf-0.1.37.dist-info/METADATA,sha256=R4NaabFRS7T1PU4X2kp1u1x4lu4uI85UzBhyqjDOP38,6787
|
|
125
|
+
vibesurf-0.1.37.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
126
|
+
vibesurf-0.1.37.dist-info/entry_points.txt,sha256=UxqpvMocL-PR33S6vLF2OmXn-kVzM-DneMeZeHcPMM8,48
|
|
127
|
+
vibesurf-0.1.37.dist-info/top_level.txt,sha256=VPZGHqSb6EEqcJ4ZX6bHIuWfon5f6HXl3c7BYpbRqnY,10
|
|
128
|
+
vibesurf-0.1.37.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|