code-puppy 0.0.177__py3-none-any.whl → 0.0.179__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.
- code_puppy/agents/agent_golang_reviewer.py +61 -0
- code_puppy/agents/base_agent.py +8 -11
- code_puppy/main.py +2 -0
- code_puppy/tui/app.py +2 -0
- {code_puppy-0.0.177.dist-info → code_puppy-0.0.179.dist-info}/METADATA +1 -1
- {code_puppy-0.0.177.dist-info → code_puppy-0.0.179.dist-info}/RECORD +10 -16
- code_puppy/tools/browser_control.py +0 -293
- code_puppy/tools/browser_interactions.py +0 -552
- code_puppy/tools/browser_locators.py +0 -642
- code_puppy/tools/browser_navigation.py +0 -251
- code_puppy/tools/browser_screenshot.py +0 -278
- code_puppy/tools/browser_scripts.py +0 -472
- code_puppy/tools/browser_workflows.py +0 -223
- {code_puppy-0.0.177.data → code_puppy-0.0.179.data}/data/code_puppy/models.json +0 -0
- {code_puppy-0.0.177.dist-info → code_puppy-0.0.179.dist-info}/WHEEL +0 -0
- {code_puppy-0.0.177.dist-info → code_puppy-0.0.179.dist-info}/entry_points.txt +0 -0
- {code_puppy-0.0.177.dist-info → code_puppy-0.0.179.dist-info}/licenses/LICENSE +0 -0
@@ -1,472 +0,0 @@
|
|
1
|
-
"""JavaScript execution and advanced page manipulation tools."""
|
2
|
-
|
3
|
-
from typing import Any, Dict, Optional
|
4
|
-
|
5
|
-
from pydantic_ai import RunContext
|
6
|
-
|
7
|
-
from code_puppy.messaging import emit_info
|
8
|
-
from code_puppy.tools.common import generate_group_id
|
9
|
-
|
10
|
-
from .camoufox_manager import get_camoufox_manager
|
11
|
-
|
12
|
-
|
13
|
-
async def execute_javascript(
|
14
|
-
script: str,
|
15
|
-
timeout: int = 30000,
|
16
|
-
) -> Dict[str, Any]:
|
17
|
-
"""Execute JavaScript code in the browser context."""
|
18
|
-
group_id = generate_group_id("browser_execute_js", script[:100])
|
19
|
-
emit_info(
|
20
|
-
f"[bold white on blue] BROWSER EXECUTE JS [/bold white on blue] 📜 script='{script[:100]}{'...' if len(script) > 100 else ''}'",
|
21
|
-
message_group=group_id,
|
22
|
-
)
|
23
|
-
try:
|
24
|
-
browser_manager = get_camoufox_manager()
|
25
|
-
page = await browser_manager.get_current_page()
|
26
|
-
|
27
|
-
if not page:
|
28
|
-
return {"success": False, "error": "No active browser page available"}
|
29
|
-
|
30
|
-
# Execute JavaScript
|
31
|
-
result = await page.evaluate(script, timeout=timeout)
|
32
|
-
|
33
|
-
emit_info(
|
34
|
-
"[green]JavaScript executed successfully[/green]", message_group=group_id
|
35
|
-
)
|
36
|
-
|
37
|
-
return {"success": True, "script": script, "result": result}
|
38
|
-
|
39
|
-
except Exception as e:
|
40
|
-
emit_info(
|
41
|
-
f"[red]JavaScript execution failed: {str(e)}[/red]", message_group=group_id
|
42
|
-
)
|
43
|
-
return {"success": False, "error": str(e), "script": script}
|
44
|
-
|
45
|
-
|
46
|
-
async def scroll_page(
|
47
|
-
direction: str = "down",
|
48
|
-
amount: int = 3,
|
49
|
-
element_selector: Optional[str] = None,
|
50
|
-
) -> Dict[str, Any]:
|
51
|
-
"""Scroll the page or a specific element."""
|
52
|
-
target = element_selector or "page"
|
53
|
-
group_id = generate_group_id("browser_scroll", f"{direction}_{amount}_{target}")
|
54
|
-
emit_info(
|
55
|
-
f"[bold white on blue] BROWSER SCROLL [/bold white on blue] 📋 direction={direction} amount={amount} target='{target}'",
|
56
|
-
message_group=group_id,
|
57
|
-
)
|
58
|
-
try:
|
59
|
-
browser_manager = get_camoufox_manager()
|
60
|
-
page = await browser_manager.get_current_page()
|
61
|
-
|
62
|
-
if not page:
|
63
|
-
return {"success": False, "error": "No active browser page available"}
|
64
|
-
|
65
|
-
if element_selector:
|
66
|
-
# Scroll specific element
|
67
|
-
element = page.locator(element_selector)
|
68
|
-
await element.scroll_into_view_if_needed()
|
69
|
-
|
70
|
-
# Get element's current scroll position and dimensions
|
71
|
-
scroll_info = await element.evaluate("""
|
72
|
-
el => {
|
73
|
-
const rect = el.getBoundingClientRect();
|
74
|
-
return {
|
75
|
-
scrollTop: el.scrollTop,
|
76
|
-
scrollLeft: el.scrollLeft,
|
77
|
-
scrollHeight: el.scrollHeight,
|
78
|
-
scrollWidth: el.scrollWidth,
|
79
|
-
clientHeight: el.clientHeight,
|
80
|
-
clientWidth: el.clientWidth
|
81
|
-
};
|
82
|
-
}
|
83
|
-
""")
|
84
|
-
|
85
|
-
# Calculate scroll amount based on element size
|
86
|
-
scroll_amount = scroll_info["clientHeight"] * amount / 3
|
87
|
-
|
88
|
-
if direction.lower() == "down":
|
89
|
-
await element.evaluate(f"el => el.scrollTop += {scroll_amount}")
|
90
|
-
elif direction.lower() == "up":
|
91
|
-
await element.evaluate(f"el => el.scrollTop -= {scroll_amount}")
|
92
|
-
elif direction.lower() == "left":
|
93
|
-
await element.evaluate(f"el => el.scrollLeft -= {scroll_amount}")
|
94
|
-
elif direction.lower() == "right":
|
95
|
-
await element.evaluate(f"el => el.scrollLeft += {scroll_amount}")
|
96
|
-
|
97
|
-
target = f"element '{element_selector}'"
|
98
|
-
|
99
|
-
else:
|
100
|
-
# Scroll page
|
101
|
-
viewport_height = await page.evaluate("() => window.innerHeight")
|
102
|
-
scroll_amount = viewport_height * amount / 3
|
103
|
-
|
104
|
-
if direction.lower() == "down":
|
105
|
-
await page.evaluate(f"window.scrollBy(0, {scroll_amount})")
|
106
|
-
elif direction.lower() == "up":
|
107
|
-
await page.evaluate(f"window.scrollBy(0, -{scroll_amount})")
|
108
|
-
elif direction.lower() == "left":
|
109
|
-
await page.evaluate(f"window.scrollBy(-{scroll_amount}, 0)")
|
110
|
-
elif direction.lower() == "right":
|
111
|
-
await page.evaluate(f"window.scrollBy({scroll_amount}, 0)")
|
112
|
-
|
113
|
-
target = "page"
|
114
|
-
|
115
|
-
# Get current scroll position
|
116
|
-
scroll_pos = await page.evaluate("""
|
117
|
-
() => ({
|
118
|
-
x: window.pageXOffset,
|
119
|
-
y: window.pageYOffset
|
120
|
-
})
|
121
|
-
""")
|
122
|
-
|
123
|
-
emit_info(
|
124
|
-
f"[green]Scrolled {target} {direction}[/green]", message_group=group_id
|
125
|
-
)
|
126
|
-
|
127
|
-
return {
|
128
|
-
"success": True,
|
129
|
-
"direction": direction,
|
130
|
-
"amount": amount,
|
131
|
-
"target": target,
|
132
|
-
"scroll_position": scroll_pos,
|
133
|
-
}
|
134
|
-
|
135
|
-
except Exception as e:
|
136
|
-
return {
|
137
|
-
"success": False,
|
138
|
-
"error": str(e),
|
139
|
-
"direction": direction,
|
140
|
-
"element_selector": element_selector,
|
141
|
-
}
|
142
|
-
|
143
|
-
|
144
|
-
async def scroll_to_element(
|
145
|
-
selector: str,
|
146
|
-
timeout: int = 10000,
|
147
|
-
) -> Dict[str, Any]:
|
148
|
-
"""Scroll to bring an element into view."""
|
149
|
-
group_id = generate_group_id("browser_scroll_to_element", selector[:100])
|
150
|
-
emit_info(
|
151
|
-
f"[bold white on blue] BROWSER SCROLL TO ELEMENT [/bold white on blue] 🎯 selector='{selector}'",
|
152
|
-
message_group=group_id,
|
153
|
-
)
|
154
|
-
try:
|
155
|
-
browser_manager = get_camoufox_manager()
|
156
|
-
page = await browser_manager.get_current_page()
|
157
|
-
|
158
|
-
if not page:
|
159
|
-
return {"success": False, "error": "No active browser page available"}
|
160
|
-
|
161
|
-
element = page.locator(selector)
|
162
|
-
await element.wait_for(state="attached", timeout=timeout)
|
163
|
-
await element.scroll_into_view_if_needed()
|
164
|
-
|
165
|
-
# Check if element is now visible
|
166
|
-
is_visible = await element.is_visible()
|
167
|
-
|
168
|
-
emit_info(
|
169
|
-
f"[green]Scrolled to element: {selector}[/green]", message_group=group_id
|
170
|
-
)
|
171
|
-
|
172
|
-
return {"success": True, "selector": selector, "visible": is_visible}
|
173
|
-
|
174
|
-
except Exception as e:
|
175
|
-
return {"success": False, "error": str(e), "selector": selector}
|
176
|
-
|
177
|
-
|
178
|
-
async def set_viewport_size(
|
179
|
-
width: int,
|
180
|
-
height: int,
|
181
|
-
) -> Dict[str, Any]:
|
182
|
-
"""Set the viewport size."""
|
183
|
-
group_id = generate_group_id("browser_set_viewport", f"{width}x{height}")
|
184
|
-
emit_info(
|
185
|
-
f"[bold white on blue] BROWSER SET VIEWPORT [/bold white on blue] 🖥️ size={width}x{height}",
|
186
|
-
message_group=group_id,
|
187
|
-
)
|
188
|
-
try:
|
189
|
-
browser_manager = get_camoufox_manager()
|
190
|
-
page = await browser_manager.get_current_page()
|
191
|
-
|
192
|
-
if not page:
|
193
|
-
return {"success": False, "error": "No active browser page available"}
|
194
|
-
|
195
|
-
await page.set_viewport_size({"width": width, "height": height})
|
196
|
-
|
197
|
-
emit_info(
|
198
|
-
f"[green]Set viewport size to {width}x{height}[/green]",
|
199
|
-
message_group=group_id,
|
200
|
-
)
|
201
|
-
|
202
|
-
return {"success": True, "width": width, "height": height}
|
203
|
-
|
204
|
-
except Exception as e:
|
205
|
-
return {"success": False, "error": str(e), "width": width, "height": height}
|
206
|
-
|
207
|
-
|
208
|
-
async def wait_for_element(
|
209
|
-
selector: str,
|
210
|
-
state: str = "visible",
|
211
|
-
timeout: int = 30000,
|
212
|
-
) -> Dict[str, Any]:
|
213
|
-
"""Wait for an element to reach a specific state."""
|
214
|
-
group_id = generate_group_id("browser_wait_for_element", f"{selector[:50]}_{state}")
|
215
|
-
emit_info(
|
216
|
-
f"[bold white on blue] BROWSER WAIT FOR ELEMENT [/bold white on blue] ⏱️ selector='{selector}' state={state} timeout={timeout}ms",
|
217
|
-
message_group=group_id,
|
218
|
-
)
|
219
|
-
try:
|
220
|
-
browser_manager = get_camoufox_manager()
|
221
|
-
page = await browser_manager.get_current_page()
|
222
|
-
|
223
|
-
if not page:
|
224
|
-
return {"success": False, "error": "No active browser page available"}
|
225
|
-
|
226
|
-
element = page.locator(selector)
|
227
|
-
await element.wait_for(state=state, timeout=timeout)
|
228
|
-
|
229
|
-
emit_info(
|
230
|
-
f"[green]Element {selector} is now {state}[/green]", message_group=group_id
|
231
|
-
)
|
232
|
-
|
233
|
-
return {"success": True, "selector": selector, "state": state}
|
234
|
-
|
235
|
-
except Exception as e:
|
236
|
-
return {"success": False, "error": str(e), "selector": selector, "state": state}
|
237
|
-
|
238
|
-
|
239
|
-
async def highlight_element(
|
240
|
-
selector: str,
|
241
|
-
color: str = "red",
|
242
|
-
timeout: int = 10000,
|
243
|
-
) -> Dict[str, Any]:
|
244
|
-
"""Highlight an element with a colored border."""
|
245
|
-
group_id = generate_group_id(
|
246
|
-
"browser_highlight_element", f"{selector[:50]}_{color}"
|
247
|
-
)
|
248
|
-
emit_info(
|
249
|
-
f"[bold white on blue] BROWSER HIGHLIGHT ELEMENT [/bold white on blue] 🔦 selector='{selector}' color={color}",
|
250
|
-
message_group=group_id,
|
251
|
-
)
|
252
|
-
try:
|
253
|
-
browser_manager = get_camoufox_manager()
|
254
|
-
page = await browser_manager.get_current_page()
|
255
|
-
|
256
|
-
if not page:
|
257
|
-
return {"success": False, "error": "No active browser page available"}
|
258
|
-
|
259
|
-
element = page.locator(selector)
|
260
|
-
await element.wait_for(state="visible", timeout=timeout)
|
261
|
-
|
262
|
-
# Add highlight style
|
263
|
-
highlight_script = f"""
|
264
|
-
el => {{
|
265
|
-
el.style.outline = '3px solid {color}';
|
266
|
-
el.style.outlineOffset = '2px';
|
267
|
-
el.style.backgroundColor = '{color}20'; // 20% opacity
|
268
|
-
el.setAttribute('data-highlighted', 'true');
|
269
|
-
}}
|
270
|
-
"""
|
271
|
-
|
272
|
-
await element.evaluate(highlight_script)
|
273
|
-
|
274
|
-
emit_info(
|
275
|
-
f"[green]Highlighted element: {selector}[/green]", message_group=group_id
|
276
|
-
)
|
277
|
-
|
278
|
-
return {"success": True, "selector": selector, "color": color}
|
279
|
-
|
280
|
-
except Exception as e:
|
281
|
-
return {"success": False, "error": str(e), "selector": selector}
|
282
|
-
|
283
|
-
|
284
|
-
async def clear_highlights() -> Dict[str, Any]:
|
285
|
-
"""Clear all element highlights."""
|
286
|
-
group_id = generate_group_id("browser_clear_highlights")
|
287
|
-
emit_info(
|
288
|
-
"[bold white on blue] BROWSER CLEAR HIGHLIGHTS [/bold white on blue] 🧹",
|
289
|
-
message_group=group_id,
|
290
|
-
)
|
291
|
-
try:
|
292
|
-
browser_manager = get_camoufox_manager()
|
293
|
-
page = await browser_manager.get_current_page()
|
294
|
-
|
295
|
-
if not page:
|
296
|
-
return {"success": False, "error": "No active browser page available"}
|
297
|
-
|
298
|
-
# Remove all highlights
|
299
|
-
clear_script = """
|
300
|
-
() => {
|
301
|
-
const highlighted = document.querySelectorAll('[data-highlighted="true"]');
|
302
|
-
highlighted.forEach(el => {
|
303
|
-
el.style.outline = '';
|
304
|
-
el.style.outlineOffset = '';
|
305
|
-
el.style.backgroundColor = '';
|
306
|
-
el.removeAttribute('data-highlighted');
|
307
|
-
});
|
308
|
-
return highlighted.length;
|
309
|
-
}
|
310
|
-
"""
|
311
|
-
|
312
|
-
count = await page.evaluate(clear_script)
|
313
|
-
|
314
|
-
emit_info(f"[green]Cleared {count} highlights[/green]", message_group=group_id)
|
315
|
-
|
316
|
-
return {"success": True, "cleared_count": count}
|
317
|
-
|
318
|
-
except Exception as e:
|
319
|
-
return {"success": False, "error": str(e)}
|
320
|
-
|
321
|
-
|
322
|
-
# Tool registration functions
|
323
|
-
def register_execute_javascript(agent):
|
324
|
-
"""Register the JavaScript execution tool."""
|
325
|
-
|
326
|
-
@agent.tool
|
327
|
-
async def browser_execute_js(
|
328
|
-
context: RunContext,
|
329
|
-
script: str,
|
330
|
-
timeout: int = 30000,
|
331
|
-
) -> Dict[str, Any]:
|
332
|
-
"""
|
333
|
-
Execute JavaScript code in the browser context.
|
334
|
-
|
335
|
-
Args:
|
336
|
-
script: JavaScript code to execute
|
337
|
-
timeout: Timeout in milliseconds
|
338
|
-
|
339
|
-
Returns:
|
340
|
-
Dict with execution results
|
341
|
-
"""
|
342
|
-
return await execute_javascript(script, timeout)
|
343
|
-
|
344
|
-
|
345
|
-
def register_scroll_page(agent):
|
346
|
-
"""Register the scroll page tool."""
|
347
|
-
|
348
|
-
@agent.tool
|
349
|
-
async def browser_scroll(
|
350
|
-
context: RunContext,
|
351
|
-
direction: str = "down",
|
352
|
-
amount: int = 3,
|
353
|
-
element_selector: Optional[str] = None,
|
354
|
-
) -> Dict[str, Any]:
|
355
|
-
"""
|
356
|
-
Scroll the page or a specific element.
|
357
|
-
|
358
|
-
Args:
|
359
|
-
direction: Scroll direction (up, down, left, right)
|
360
|
-
amount: Scroll amount multiplier (1-10)
|
361
|
-
element_selector: Optional selector to scroll specific element
|
362
|
-
|
363
|
-
Returns:
|
364
|
-
Dict with scroll results
|
365
|
-
"""
|
366
|
-
return await scroll_page(direction, amount, element_selector)
|
367
|
-
|
368
|
-
|
369
|
-
def register_scroll_to_element(agent):
|
370
|
-
"""Register the scroll to element tool."""
|
371
|
-
|
372
|
-
@agent.tool
|
373
|
-
async def browser_scroll_to_element(
|
374
|
-
context: RunContext,
|
375
|
-
selector: str,
|
376
|
-
timeout: int = 10000,
|
377
|
-
) -> Dict[str, Any]:
|
378
|
-
"""
|
379
|
-
Scroll to bring an element into view.
|
380
|
-
|
381
|
-
Args:
|
382
|
-
selector: CSS or XPath selector for the element
|
383
|
-
timeout: Timeout in milliseconds
|
384
|
-
|
385
|
-
Returns:
|
386
|
-
Dict with scroll results
|
387
|
-
"""
|
388
|
-
return await scroll_to_element(selector, timeout)
|
389
|
-
|
390
|
-
|
391
|
-
def register_set_viewport_size(agent):
|
392
|
-
"""Register the viewport size tool."""
|
393
|
-
|
394
|
-
@agent.tool
|
395
|
-
async def browser_set_viewport(
|
396
|
-
context: RunContext,
|
397
|
-
width: int,
|
398
|
-
height: int,
|
399
|
-
) -> Dict[str, Any]:
|
400
|
-
"""
|
401
|
-
Set the browser viewport size.
|
402
|
-
|
403
|
-
Args:
|
404
|
-
width: Viewport width in pixels
|
405
|
-
height: Viewport height in pixels
|
406
|
-
|
407
|
-
Returns:
|
408
|
-
Dict with viewport size results
|
409
|
-
"""
|
410
|
-
return await set_viewport_size(width, height)
|
411
|
-
|
412
|
-
|
413
|
-
def register_wait_for_element(agent):
|
414
|
-
"""Register the wait for element tool."""
|
415
|
-
|
416
|
-
@agent.tool
|
417
|
-
async def browser_wait_for_element(
|
418
|
-
context: RunContext,
|
419
|
-
selector: str,
|
420
|
-
state: str = "visible",
|
421
|
-
timeout: int = 30000,
|
422
|
-
) -> Dict[str, Any]:
|
423
|
-
"""
|
424
|
-
Wait for an element to reach a specific state.
|
425
|
-
|
426
|
-
Args:
|
427
|
-
selector: CSS or XPath selector for the element
|
428
|
-
state: State to wait for (visible, hidden, attached, detached)
|
429
|
-
timeout: Timeout in milliseconds
|
430
|
-
|
431
|
-
Returns:
|
432
|
-
Dict with wait results
|
433
|
-
"""
|
434
|
-
return await wait_for_element(selector, state, timeout)
|
435
|
-
|
436
|
-
|
437
|
-
def register_browser_highlight_element(agent):
|
438
|
-
"""Register the element highlighting tool."""
|
439
|
-
|
440
|
-
@agent.tool
|
441
|
-
async def browser_highlight_element(
|
442
|
-
context: RunContext,
|
443
|
-
selector: str,
|
444
|
-
color: str = "red",
|
445
|
-
timeout: int = 10000,
|
446
|
-
) -> Dict[str, Any]:
|
447
|
-
"""
|
448
|
-
Highlight an element with a colored border for visual identification.
|
449
|
-
|
450
|
-
Args:
|
451
|
-
selector: CSS or XPath selector for the element
|
452
|
-
color: Highlight color (red, blue, green, yellow, etc.)
|
453
|
-
timeout: Timeout in milliseconds
|
454
|
-
|
455
|
-
Returns:
|
456
|
-
Dict with highlight results
|
457
|
-
"""
|
458
|
-
return await highlight_element(selector, color, timeout)
|
459
|
-
|
460
|
-
|
461
|
-
def register_browser_clear_highlights(agent):
|
462
|
-
"""Register the clear highlights tool."""
|
463
|
-
|
464
|
-
@agent.tool
|
465
|
-
async def browser_clear_highlights(context: RunContext) -> Dict[str, Any]:
|
466
|
-
"""
|
467
|
-
Clear all element highlights from the page.
|
468
|
-
|
469
|
-
Returns:
|
470
|
-
Dict with clear results
|
471
|
-
"""
|
472
|
-
return await clear_highlights()
|
@@ -1,223 +0,0 @@
|
|
1
|
-
"""Browser workflow management tools for saving and reusing automation patterns."""
|
2
|
-
|
3
|
-
from pathlib import Path
|
4
|
-
from typing import Any, Dict
|
5
|
-
|
6
|
-
from pydantic_ai import RunContext
|
7
|
-
|
8
|
-
from code_puppy.messaging import emit_info
|
9
|
-
from code_puppy.tools.common import generate_group_id
|
10
|
-
|
11
|
-
|
12
|
-
def get_workflows_directory() -> Path:
|
13
|
-
"""Get the browser workflows directory, creating it if it doesn't exist."""
|
14
|
-
home_dir = Path.home()
|
15
|
-
workflows_dir = home_dir / ".code_puppy" / "browser_workflows"
|
16
|
-
workflows_dir.mkdir(parents=True, exist_ok=True)
|
17
|
-
return workflows_dir
|
18
|
-
|
19
|
-
|
20
|
-
async def save_workflow(name: str, content: str) -> Dict[str, Any]:
|
21
|
-
"""Save a browser workflow as a markdown file."""
|
22
|
-
group_id = generate_group_id("save_workflow", name)
|
23
|
-
emit_info(
|
24
|
-
f"[bold white on blue] SAVE WORKFLOW [/bold white on blue] 💾 name='{name}'",
|
25
|
-
message_group=group_id,
|
26
|
-
)
|
27
|
-
|
28
|
-
try:
|
29
|
-
workflows_dir = get_workflows_directory()
|
30
|
-
|
31
|
-
# Clean up the filename - remove spaces, special chars, etc.
|
32
|
-
safe_name = "".join(c for c in name if c.isalnum() or c in ("-", "_")).lower()
|
33
|
-
if not safe_name:
|
34
|
-
safe_name = "workflow"
|
35
|
-
|
36
|
-
# Ensure .md extension
|
37
|
-
if not safe_name.endswith(".md"):
|
38
|
-
safe_name += ".md"
|
39
|
-
|
40
|
-
workflow_path = workflows_dir / safe_name
|
41
|
-
|
42
|
-
# Write the workflow content
|
43
|
-
with open(workflow_path, "w", encoding="utf-8") as f:
|
44
|
-
f.write(content)
|
45
|
-
|
46
|
-
emit_info(
|
47
|
-
f"[green]✅ Workflow saved successfully: {workflow_path}[/green]",
|
48
|
-
message_group=group_id,
|
49
|
-
)
|
50
|
-
|
51
|
-
return {
|
52
|
-
"success": True,
|
53
|
-
"path": str(workflow_path),
|
54
|
-
"name": safe_name,
|
55
|
-
"size": len(content),
|
56
|
-
}
|
57
|
-
|
58
|
-
except Exception as e:
|
59
|
-
emit_info(
|
60
|
-
f"[red]❌ Failed to save workflow: {e}[/red]",
|
61
|
-
message_group=group_id,
|
62
|
-
)
|
63
|
-
return {"success": False, "error": str(e), "name": name}
|
64
|
-
|
65
|
-
|
66
|
-
async def list_workflows() -> Dict[str, Any]:
|
67
|
-
"""List all available browser workflows."""
|
68
|
-
group_id = generate_group_id("list_workflows")
|
69
|
-
emit_info(
|
70
|
-
"[bold white on blue] LIST WORKFLOWS [/bold white on blue] 📋",
|
71
|
-
message_group=group_id,
|
72
|
-
)
|
73
|
-
|
74
|
-
try:
|
75
|
-
workflows_dir = get_workflows_directory()
|
76
|
-
|
77
|
-
# Find all .md files in the workflows directory
|
78
|
-
workflow_files = list(workflows_dir.glob("*.md"))
|
79
|
-
|
80
|
-
workflows = []
|
81
|
-
for workflow_file in workflow_files:
|
82
|
-
try:
|
83
|
-
stat = workflow_file.stat()
|
84
|
-
workflows.append(
|
85
|
-
{
|
86
|
-
"name": workflow_file.name,
|
87
|
-
"path": str(workflow_file),
|
88
|
-
"size": stat.st_size,
|
89
|
-
"modified": stat.st_mtime,
|
90
|
-
}
|
91
|
-
)
|
92
|
-
except Exception as e:
|
93
|
-
emit_info(
|
94
|
-
f"[yellow]Warning: Could not read {workflow_file}: {e}[/yellow]"
|
95
|
-
)
|
96
|
-
|
97
|
-
# Sort by modification time (newest first)
|
98
|
-
workflows.sort(key=lambda x: x["modified"], reverse=True)
|
99
|
-
|
100
|
-
emit_info(
|
101
|
-
f"[green]✅ Found {len(workflows)} workflow(s)[/green]",
|
102
|
-
message_group=group_id,
|
103
|
-
)
|
104
|
-
|
105
|
-
return {
|
106
|
-
"success": True,
|
107
|
-
"workflows": workflows,
|
108
|
-
"count": len(workflows),
|
109
|
-
"directory": str(workflows_dir),
|
110
|
-
}
|
111
|
-
|
112
|
-
except Exception as e:
|
113
|
-
emit_info(
|
114
|
-
f"[red]❌ Failed to list workflows: {e}[/red]",
|
115
|
-
message_group=group_id,
|
116
|
-
)
|
117
|
-
return {"success": False, "error": str(e)}
|
118
|
-
|
119
|
-
|
120
|
-
async def read_workflow(name: str) -> Dict[str, Any]:
|
121
|
-
"""Read a saved browser workflow."""
|
122
|
-
group_id = generate_group_id("read_workflow", name)
|
123
|
-
emit_info(
|
124
|
-
f"[bold white on blue] READ WORKFLOW [/bold white on blue] 📖 name='{name}'",
|
125
|
-
message_group=group_id,
|
126
|
-
)
|
127
|
-
|
128
|
-
try:
|
129
|
-
workflows_dir = get_workflows_directory()
|
130
|
-
|
131
|
-
# Handle both with and without .md extension
|
132
|
-
if not name.endswith(".md"):
|
133
|
-
name += ".md"
|
134
|
-
|
135
|
-
workflow_path = workflows_dir / name
|
136
|
-
|
137
|
-
if not workflow_path.exists():
|
138
|
-
emit_info(
|
139
|
-
f"[red]❌ Workflow not found: {name}[/red]",
|
140
|
-
message_group=group_id,
|
141
|
-
)
|
142
|
-
return {
|
143
|
-
"success": False,
|
144
|
-
"error": f"Workflow '{name}' not found",
|
145
|
-
"name": name,
|
146
|
-
}
|
147
|
-
|
148
|
-
# Read the workflow content
|
149
|
-
with open(workflow_path, "r", encoding="utf-8") as f:
|
150
|
-
content = f.read()
|
151
|
-
|
152
|
-
emit_info(
|
153
|
-
f"[green]✅ Workflow read successfully: {len(content)} characters[/green]",
|
154
|
-
message_group=group_id,
|
155
|
-
)
|
156
|
-
|
157
|
-
return {
|
158
|
-
"success": True,
|
159
|
-
"name": name,
|
160
|
-
"content": content,
|
161
|
-
"path": str(workflow_path),
|
162
|
-
"size": len(content),
|
163
|
-
}
|
164
|
-
|
165
|
-
except Exception as e:
|
166
|
-
emit_info(
|
167
|
-
f"[red]❌ Failed to read workflow: {e}[/red]",
|
168
|
-
message_group=group_id,
|
169
|
-
)
|
170
|
-
return {"success": False, "error": str(e), "name": name}
|
171
|
-
|
172
|
-
|
173
|
-
def register_save_workflow(agent):
|
174
|
-
"""Register the save workflow tool."""
|
175
|
-
|
176
|
-
async def save_workflow_tool(
|
177
|
-
context: RunContext,
|
178
|
-
name: str,
|
179
|
-
content: str,
|
180
|
-
) -> Dict[str, Any]:
|
181
|
-
"""
|
182
|
-
Save a browser automation workflow as a markdown file.
|
183
|
-
|
184
|
-
Args:
|
185
|
-
name: Name for the workflow (will be sanitized for filename)
|
186
|
-
content: Markdown content describing the workflow steps
|
187
|
-
|
188
|
-
Returns:
|
189
|
-
Dict with success status and file path
|
190
|
-
"""
|
191
|
-
return await save_workflow(name, content)
|
192
|
-
|
193
|
-
|
194
|
-
def register_list_workflows(agent):
|
195
|
-
"""Register the list workflows tool."""
|
196
|
-
|
197
|
-
async def list_workflows_tool(context: RunContext) -> Dict[str, Any]:
|
198
|
-
"""
|
199
|
-
List all saved browser automation workflows.
|
200
|
-
|
201
|
-
Returns:
|
202
|
-
Dict with list of available workflows and their metadata
|
203
|
-
"""
|
204
|
-
return await list_workflows()
|
205
|
-
|
206
|
-
|
207
|
-
def register_read_workflow(agent):
|
208
|
-
"""Register the read workflow tool."""
|
209
|
-
|
210
|
-
async def read_workflow_tool(
|
211
|
-
context: RunContext,
|
212
|
-
name: str,
|
213
|
-
) -> Dict[str, Any]:
|
214
|
-
"""
|
215
|
-
Read a saved browser automation workflow.
|
216
|
-
|
217
|
-
Args:
|
218
|
-
name: Name of the workflow to read (with or without .md extension)
|
219
|
-
|
220
|
-
Returns:
|
221
|
-
Dict with workflow content and metadata
|
222
|
-
"""
|
223
|
-
return await read_workflow(name)
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|