code-puppy 0.0.287__py3-none-any.whl → 0.0.323__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/__init__.py +3 -1
- code_puppy/agents/agent_code_puppy.py +5 -4
- code_puppy/agents/agent_creator_agent.py +22 -18
- code_puppy/agents/agent_manager.py +2 -2
- code_puppy/agents/base_agent.py +496 -102
- code_puppy/callbacks.py +8 -0
- code_puppy/chatgpt_codex_client.py +283 -0
- code_puppy/cli_runner.py +795 -0
- code_puppy/command_line/add_model_menu.py +19 -16
- code_puppy/command_line/attachments.py +10 -5
- code_puppy/command_line/autosave_menu.py +269 -41
- code_puppy/command_line/colors_menu.py +515 -0
- code_puppy/command_line/command_handler.py +10 -24
- code_puppy/command_line/config_commands.py +106 -25
- code_puppy/command_line/core_commands.py +32 -20
- code_puppy/command_line/mcp/add_command.py +3 -16
- code_puppy/command_line/mcp/base.py +0 -3
- code_puppy/command_line/mcp/catalog_server_installer.py +15 -15
- code_puppy/command_line/mcp/custom_server_form.py +66 -5
- code_puppy/command_line/mcp/custom_server_installer.py +17 -17
- code_puppy/command_line/mcp/edit_command.py +15 -22
- code_puppy/command_line/mcp/handler.py +7 -2
- code_puppy/command_line/mcp/help_command.py +2 -2
- code_puppy/command_line/mcp/install_command.py +10 -14
- code_puppy/command_line/mcp/install_menu.py +2 -6
- code_puppy/command_line/mcp/list_command.py +2 -2
- code_puppy/command_line/mcp/logs_command.py +174 -65
- code_puppy/command_line/mcp/remove_command.py +2 -2
- code_puppy/command_line/mcp/restart_command.py +7 -2
- code_puppy/command_line/mcp/search_command.py +16 -10
- code_puppy/command_line/mcp/start_all_command.py +16 -6
- code_puppy/command_line/mcp/start_command.py +12 -10
- code_puppy/command_line/mcp/status_command.py +4 -5
- code_puppy/command_line/mcp/stop_all_command.py +5 -1
- code_puppy/command_line/mcp/stop_command.py +6 -4
- code_puppy/command_line/mcp/test_command.py +2 -2
- code_puppy/command_line/mcp/wizard_utils.py +20 -16
- code_puppy/command_line/model_settings_menu.py +53 -7
- code_puppy/command_line/motd.py +1 -1
- code_puppy/command_line/pin_command_completion.py +82 -7
- code_puppy/command_line/prompt_toolkit_completion.py +32 -9
- code_puppy/command_line/session_commands.py +11 -4
- code_puppy/config.py +217 -53
- code_puppy/error_logging.py +118 -0
- code_puppy/gemini_code_assist.py +385 -0
- code_puppy/keymap.py +126 -0
- code_puppy/main.py +5 -745
- code_puppy/mcp_/__init__.py +17 -0
- code_puppy/mcp_/blocking_startup.py +63 -36
- code_puppy/mcp_/captured_stdio_server.py +1 -1
- code_puppy/mcp_/config_wizard.py +4 -4
- code_puppy/mcp_/dashboard.py +15 -6
- code_puppy/mcp_/managed_server.py +25 -5
- code_puppy/mcp_/manager.py +65 -0
- code_puppy/mcp_/mcp_logs.py +224 -0
- code_puppy/mcp_/registry.py +6 -6
- code_puppy/messaging/__init__.py +184 -2
- code_puppy/messaging/bus.py +610 -0
- code_puppy/messaging/commands.py +167 -0
- code_puppy/messaging/markdown_patches.py +57 -0
- code_puppy/messaging/message_queue.py +3 -3
- code_puppy/messaging/messages.py +470 -0
- code_puppy/messaging/renderers.py +43 -141
- code_puppy/messaging/rich_renderer.py +900 -0
- code_puppy/messaging/spinner/console_spinner.py +39 -2
- code_puppy/model_factory.py +292 -53
- code_puppy/model_utils.py +57 -48
- code_puppy/models.json +19 -5
- code_puppy/plugins/__init__.py +152 -10
- code_puppy/plugins/chatgpt_oauth/config.py +20 -12
- code_puppy/plugins/chatgpt_oauth/oauth_flow.py +5 -6
- code_puppy/plugins/chatgpt_oauth/register_callbacks.py +3 -3
- code_puppy/plugins/chatgpt_oauth/test_plugin.py +30 -13
- code_puppy/plugins/chatgpt_oauth/utils.py +180 -65
- code_puppy/plugins/claude_code_oauth/config.py +15 -11
- code_puppy/plugins/claude_code_oauth/register_callbacks.py +28 -0
- code_puppy/plugins/claude_code_oauth/utils.py +6 -1
- code_puppy/plugins/example_custom_command/register_callbacks.py +2 -2
- code_puppy/plugins/oauth_puppy_html.py +3 -0
- code_puppy/plugins/shell_safety/agent_shell_safety.py +1 -134
- code_puppy/plugins/shell_safety/command_cache.py +156 -0
- code_puppy/plugins/shell_safety/register_callbacks.py +77 -3
- code_puppy/prompts/codex_system_prompt.md +310 -0
- code_puppy/pydantic_patches.py +131 -0
- code_puppy/session_storage.py +2 -1
- code_puppy/status_display.py +7 -5
- code_puppy/terminal_utils.py +126 -0
- code_puppy/tools/agent_tools.py +131 -70
- code_puppy/tools/browser/browser_control.py +10 -14
- code_puppy/tools/browser/browser_interactions.py +20 -28
- code_puppy/tools/browser/browser_locators.py +27 -29
- code_puppy/tools/browser/browser_navigation.py +9 -9
- code_puppy/tools/browser/browser_screenshot.py +12 -14
- code_puppy/tools/browser/browser_scripts.py +17 -29
- code_puppy/tools/browser/browser_workflows.py +24 -25
- code_puppy/tools/browser/camoufox_manager.py +22 -26
- code_puppy/tools/command_runner.py +410 -88
- code_puppy/tools/common.py +51 -38
- code_puppy/tools/file_modifications.py +98 -24
- code_puppy/tools/file_operations.py +113 -202
- code_puppy/version_checker.py +28 -13
- {code_puppy-0.0.287.data → code_puppy-0.0.323.data}/data/code_puppy/models.json +19 -5
- {code_puppy-0.0.287.dist-info → code_puppy-0.0.323.dist-info}/METADATA +3 -8
- code_puppy-0.0.323.dist-info/RECORD +168 -0
- code_puppy/tui_state.py +0 -55
- code_puppy-0.0.287.dist-info/RECORD +0 -153
- {code_puppy-0.0.287.data → code_puppy-0.0.323.data}/data/code_puppy/models_dev_api.json +0 -0
- {code_puppy-0.0.287.dist-info → code_puppy-0.0.323.dist-info}/WHEEL +0 -0
- {code_puppy-0.0.287.dist-info → code_puppy-0.0.323.dist-info}/entry_points.txt +0 -0
- {code_puppy-0.0.287.dist-info → code_puppy-0.0.323.dist-info}/licenses/LICENSE +0 -0
|
@@ -4,7 +4,7 @@ from typing import Any, Dict, Optional
|
|
|
4
4
|
|
|
5
5
|
from pydantic_ai import RunContext
|
|
6
6
|
|
|
7
|
-
from code_puppy.messaging import emit_info
|
|
7
|
+
from code_puppy.messaging import emit_error, emit_info, emit_success
|
|
8
8
|
from code_puppy.tools.common import generate_group_id
|
|
9
9
|
|
|
10
10
|
from .camoufox_manager import get_camoufox_manager
|
|
@@ -17,7 +17,7 @@ async def execute_javascript(
|
|
|
17
17
|
"""Execute JavaScript code in the browser context."""
|
|
18
18
|
group_id = generate_group_id("browser_execute_js", script[:100])
|
|
19
19
|
emit_info(
|
|
20
|
-
f"
|
|
20
|
+
f"BROWSER EXECUTE JS 📜 script='{script[:100]}{'...' if len(script) > 100 else ''}'",
|
|
21
21
|
message_group=group_id,
|
|
22
22
|
)
|
|
23
23
|
try:
|
|
@@ -30,16 +30,12 @@ async def execute_javascript(
|
|
|
30
30
|
# Execute JavaScript
|
|
31
31
|
result = await page.evaluate(script, timeout=timeout)
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
"[green]JavaScript executed successfully[/green]", message_group=group_id
|
|
35
|
-
)
|
|
33
|
+
emit_success("JavaScript executed successfully", message_group=group_id)
|
|
36
34
|
|
|
37
35
|
return {"success": True, "script": script, "result": result}
|
|
38
36
|
|
|
39
37
|
except Exception as e:
|
|
40
|
-
|
|
41
|
-
f"[red]JavaScript execution failed: {str(e)}[/red]", message_group=group_id
|
|
42
|
-
)
|
|
38
|
+
emit_error(f"JavaScript execution failed: {str(e)}", message_group=group_id)
|
|
43
39
|
return {"success": False, "error": str(e), "script": script}
|
|
44
40
|
|
|
45
41
|
|
|
@@ -52,7 +48,7 @@ async def scroll_page(
|
|
|
52
48
|
target = element_selector or "page"
|
|
53
49
|
group_id = generate_group_id("browser_scroll", f"{direction}_{amount}_{target}")
|
|
54
50
|
emit_info(
|
|
55
|
-
f"
|
|
51
|
+
f"BROWSER SCROLL 📋 direction={direction} amount={amount} target='{target}'",
|
|
56
52
|
message_group=group_id,
|
|
57
53
|
)
|
|
58
54
|
try:
|
|
@@ -120,9 +116,7 @@ async def scroll_page(
|
|
|
120
116
|
})
|
|
121
117
|
""")
|
|
122
118
|
|
|
123
|
-
|
|
124
|
-
f"[green]Scrolled {target} {direction}[/green]", message_group=group_id
|
|
125
|
-
)
|
|
119
|
+
emit_success(f"Scrolled {target} {direction}", message_group=group_id)
|
|
126
120
|
|
|
127
121
|
return {
|
|
128
122
|
"success": True,
|
|
@@ -148,7 +142,7 @@ async def scroll_to_element(
|
|
|
148
142
|
"""Scroll to bring an element into view."""
|
|
149
143
|
group_id = generate_group_id("browser_scroll_to_element", selector[:100])
|
|
150
144
|
emit_info(
|
|
151
|
-
f"
|
|
145
|
+
f"BROWSER SCROLL TO ELEMENT 🎯 selector='{selector}'",
|
|
152
146
|
message_group=group_id,
|
|
153
147
|
)
|
|
154
148
|
try:
|
|
@@ -165,9 +159,7 @@ async def scroll_to_element(
|
|
|
165
159
|
# Check if element is now visible
|
|
166
160
|
is_visible = await element.is_visible()
|
|
167
161
|
|
|
168
|
-
|
|
169
|
-
f"[green]Scrolled to element: {selector}[/green]", message_group=group_id
|
|
170
|
-
)
|
|
162
|
+
emit_success(f"Scrolled to element: {selector}", message_group=group_id)
|
|
171
163
|
|
|
172
164
|
return {"success": True, "selector": selector, "visible": is_visible}
|
|
173
165
|
|
|
@@ -182,7 +174,7 @@ async def set_viewport_size(
|
|
|
182
174
|
"""Set the viewport size."""
|
|
183
175
|
group_id = generate_group_id("browser_set_viewport", f"{width}x{height}")
|
|
184
176
|
emit_info(
|
|
185
|
-
f"
|
|
177
|
+
f"BROWSER SET VIEWPORT 🖥️ size={width}x{height}",
|
|
186
178
|
message_group=group_id,
|
|
187
179
|
)
|
|
188
180
|
try:
|
|
@@ -194,8 +186,8 @@ async def set_viewport_size(
|
|
|
194
186
|
|
|
195
187
|
await page.set_viewport_size({"width": width, "height": height})
|
|
196
188
|
|
|
197
|
-
|
|
198
|
-
f"
|
|
189
|
+
emit_success(
|
|
190
|
+
f"Set viewport size to {width}x{height}",
|
|
199
191
|
message_group=group_id,
|
|
200
192
|
)
|
|
201
193
|
|
|
@@ -213,7 +205,7 @@ async def wait_for_element(
|
|
|
213
205
|
"""Wait for an element to reach a specific state."""
|
|
214
206
|
group_id = generate_group_id("browser_wait_for_element", f"{selector[:50]}_{state}")
|
|
215
207
|
emit_info(
|
|
216
|
-
f"
|
|
208
|
+
f"BROWSER WAIT FOR ELEMENT ⏱️ selector='{selector}' state={state} timeout={timeout}ms",
|
|
217
209
|
message_group=group_id,
|
|
218
210
|
)
|
|
219
211
|
try:
|
|
@@ -226,9 +218,7 @@ async def wait_for_element(
|
|
|
226
218
|
element = page.locator(selector)
|
|
227
219
|
await element.wait_for(state=state, timeout=timeout)
|
|
228
220
|
|
|
229
|
-
|
|
230
|
-
f"[green]Element {selector} is now {state}[/green]", message_group=group_id
|
|
231
|
-
)
|
|
221
|
+
emit_success(f"Element {selector} is now {state}", message_group=group_id)
|
|
232
222
|
|
|
233
223
|
return {"success": True, "selector": selector, "state": state}
|
|
234
224
|
|
|
@@ -246,7 +236,7 @@ async def highlight_element(
|
|
|
246
236
|
"browser_highlight_element", f"{selector[:50]}_{color}"
|
|
247
237
|
)
|
|
248
238
|
emit_info(
|
|
249
|
-
f"
|
|
239
|
+
f"BROWSER HIGHLIGHT ELEMENT 🔦 selector='{selector}' color={color}",
|
|
250
240
|
message_group=group_id,
|
|
251
241
|
)
|
|
252
242
|
try:
|
|
@@ -271,9 +261,7 @@ async def highlight_element(
|
|
|
271
261
|
|
|
272
262
|
await element.evaluate(highlight_script)
|
|
273
263
|
|
|
274
|
-
|
|
275
|
-
f"[green]Highlighted element: {selector}[/green]", message_group=group_id
|
|
276
|
-
)
|
|
264
|
+
emit_success(f"Highlighted element: {selector}", message_group=group_id)
|
|
277
265
|
|
|
278
266
|
return {"success": True, "selector": selector, "color": color}
|
|
279
267
|
|
|
@@ -285,7 +273,7 @@ async def clear_highlights() -> Dict[str, Any]:
|
|
|
285
273
|
"""Clear all element highlights."""
|
|
286
274
|
group_id = generate_group_id("browser_clear_highlights")
|
|
287
275
|
emit_info(
|
|
288
|
-
"
|
|
276
|
+
"BROWSER CLEAR HIGHLIGHTS 🧹",
|
|
289
277
|
message_group=group_id,
|
|
290
278
|
)
|
|
291
279
|
try:
|
|
@@ -311,7 +299,7 @@ async def clear_highlights() -> Dict[str, Any]:
|
|
|
311
299
|
|
|
312
300
|
count = await page.evaluate(clear_script)
|
|
313
301
|
|
|
314
|
-
|
|
302
|
+
emit_success(f"Cleared {count} highlights", message_group=group_id)
|
|
315
303
|
|
|
316
304
|
return {"success": True, "cleared_count": count}
|
|
317
305
|
|
|
@@ -5,15 +5,16 @@ from typing import Any, Dict
|
|
|
5
5
|
|
|
6
6
|
from pydantic_ai import RunContext
|
|
7
7
|
|
|
8
|
-
from code_puppy
|
|
8
|
+
from code_puppy import config
|
|
9
|
+
from code_puppy.messaging import emit_error, emit_info, emit_success, emit_warning
|
|
9
10
|
from code_puppy.tools.common import generate_group_id
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
def get_workflows_directory() -> Path:
|
|
13
|
-
"""Get the browser workflows directory, creating it if it doesn't exist."""
|
|
14
|
-
|
|
15
|
-
workflows_dir =
|
|
16
|
-
workflows_dir.mkdir(parents=True, exist_ok=True)
|
|
14
|
+
"""Get the browser workflows directory, creating it if it doesn't exist (uses XDG_DATA_HOME)."""
|
|
15
|
+
data_dir = Path(config.DATA_DIR)
|
|
16
|
+
workflows_dir = data_dir / "browser_workflows"
|
|
17
|
+
workflows_dir.mkdir(parents=True, exist_ok=True, mode=0o700)
|
|
17
18
|
return workflows_dir
|
|
18
19
|
|
|
19
20
|
|
|
@@ -21,7 +22,7 @@ async def save_workflow(name: str, content: str) -> Dict[str, Any]:
|
|
|
21
22
|
"""Save a browser workflow as a markdown file."""
|
|
22
23
|
group_id = generate_group_id("save_workflow", name)
|
|
23
24
|
emit_info(
|
|
24
|
-
f"
|
|
25
|
+
f"SAVE WORKFLOW 💾 name='{name}'",
|
|
25
26
|
message_group=group_id,
|
|
26
27
|
)
|
|
27
28
|
|
|
@@ -61,8 +62,8 @@ async def save_workflow(name: str, content: str) -> Dict[str, Any]:
|
|
|
61
62
|
with open(workflow_path, "w", encoding="utf-8") as f:
|
|
62
63
|
f.write(content)
|
|
63
64
|
|
|
64
|
-
|
|
65
|
-
f"
|
|
65
|
+
emit_success(
|
|
66
|
+
f"Workflow saved successfully: {workflow_path}",
|
|
66
67
|
message_group=group_id,
|
|
67
68
|
)
|
|
68
69
|
|
|
@@ -74,8 +75,8 @@ async def save_workflow(name: str, content: str) -> Dict[str, Any]:
|
|
|
74
75
|
}
|
|
75
76
|
|
|
76
77
|
except Exception as e:
|
|
77
|
-
|
|
78
|
-
f"
|
|
78
|
+
emit_error(
|
|
79
|
+
f"Failed to save workflow: {e}",
|
|
79
80
|
message_group=group_id,
|
|
80
81
|
)
|
|
81
82
|
return {"success": False, "error": str(e), "name": name}
|
|
@@ -85,7 +86,7 @@ async def list_workflows() -> Dict[str, Any]:
|
|
|
85
86
|
"""List all available browser workflows."""
|
|
86
87
|
group_id = generate_group_id("list_workflows")
|
|
87
88
|
emit_info(
|
|
88
|
-
"
|
|
89
|
+
"LIST WORKFLOWS 📋",
|
|
89
90
|
message_group=group_id,
|
|
90
91
|
)
|
|
91
92
|
|
|
@@ -108,15 +109,13 @@ async def list_workflows() -> Dict[str, Any]:
|
|
|
108
109
|
}
|
|
109
110
|
)
|
|
110
111
|
except Exception as e:
|
|
111
|
-
|
|
112
|
-
f"[yellow]Warning: Could not read {workflow_file}: {e}[/yellow]"
|
|
113
|
-
)
|
|
112
|
+
emit_warning(f"Could not read {workflow_file}: {e}")
|
|
114
113
|
|
|
115
114
|
# Sort by modification time (newest first)
|
|
116
115
|
workflows.sort(key=lambda x: x["modified"], reverse=True)
|
|
117
116
|
|
|
118
|
-
|
|
119
|
-
f"
|
|
117
|
+
emit_success(
|
|
118
|
+
f"Found {len(workflows)} workflow(s)",
|
|
120
119
|
message_group=group_id,
|
|
121
120
|
)
|
|
122
121
|
|
|
@@ -128,8 +127,8 @@ async def list_workflows() -> Dict[str, Any]:
|
|
|
128
127
|
}
|
|
129
128
|
|
|
130
129
|
except Exception as e:
|
|
131
|
-
|
|
132
|
-
f"
|
|
130
|
+
emit_error(
|
|
131
|
+
f"Failed to list workflows: {e}",
|
|
133
132
|
message_group=group_id,
|
|
134
133
|
)
|
|
135
134
|
return {"success": False, "error": str(e)}
|
|
@@ -139,7 +138,7 @@ async def read_workflow(name: str) -> Dict[str, Any]:
|
|
|
139
138
|
"""Read a saved browser workflow."""
|
|
140
139
|
group_id = generate_group_id("read_workflow", name)
|
|
141
140
|
emit_info(
|
|
142
|
-
f"
|
|
141
|
+
f"READ WORKFLOW 📖 name='{name}'",
|
|
143
142
|
message_group=group_id,
|
|
144
143
|
)
|
|
145
144
|
|
|
@@ -153,8 +152,8 @@ async def read_workflow(name: str) -> Dict[str, Any]:
|
|
|
153
152
|
workflow_path = workflows_dir / name
|
|
154
153
|
|
|
155
154
|
if not workflow_path.exists():
|
|
156
|
-
|
|
157
|
-
f"
|
|
155
|
+
emit_error(
|
|
156
|
+
f"Workflow not found: {name}",
|
|
158
157
|
message_group=group_id,
|
|
159
158
|
)
|
|
160
159
|
return {
|
|
@@ -167,8 +166,8 @@ async def read_workflow(name: str) -> Dict[str, Any]:
|
|
|
167
166
|
with open(workflow_path, "r", encoding="utf-8") as f:
|
|
168
167
|
content = f.read()
|
|
169
168
|
|
|
170
|
-
|
|
171
|
-
f"
|
|
169
|
+
emit_success(
|
|
170
|
+
f"Workflow read successfully: {len(content)} characters",
|
|
172
171
|
message_group=group_id,
|
|
173
172
|
)
|
|
174
173
|
|
|
@@ -181,8 +180,8 @@ async def read_workflow(name: str) -> Dict[str, Any]:
|
|
|
181
180
|
}
|
|
182
181
|
|
|
183
182
|
except Exception as e:
|
|
184
|
-
|
|
185
|
-
f"
|
|
183
|
+
emit_error(
|
|
184
|
+
f"Failed to read workflow: {e}",
|
|
186
185
|
message_group=group_id,
|
|
187
186
|
)
|
|
188
187
|
return {"success": False, "error": str(e), "name": name}
|
|
@@ -6,7 +6,8 @@ from typing import Optional
|
|
|
6
6
|
|
|
7
7
|
from playwright.async_api import Browser, BrowserContext, Page
|
|
8
8
|
|
|
9
|
-
from code_puppy
|
|
9
|
+
from code_puppy import config
|
|
10
|
+
from code_puppy.messaging import emit_info, emit_success, emit_warning
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
class CamoufoxManager:
|
|
@@ -48,13 +49,14 @@ class CamoufoxManager:
|
|
|
48
49
|
return cls._instance
|
|
49
50
|
|
|
50
51
|
def _get_profile_directory(self) -> Path:
|
|
51
|
-
"""Get or create the persistent profile directory.
|
|
52
|
+
"""Get or create the persistent profile directory (uses XDG_CACHE_HOME).
|
|
52
53
|
|
|
53
|
-
Returns a Path object pointing to
|
|
54
|
+
Returns a Path object pointing to XDG_CACHE_HOME/code_puppy/camoufox_profile
|
|
54
55
|
where browser data (cookies, history, bookmarks, etc.) will be stored.
|
|
55
56
|
"""
|
|
56
|
-
|
|
57
|
-
profile_path
|
|
57
|
+
cache_dir = Path(config.CACHE_DIR)
|
|
58
|
+
profile_path = cache_dir / "camoufox_profile"
|
|
59
|
+
profile_path.mkdir(parents=True, exist_ok=True, mode=0o700)
|
|
58
60
|
return profile_path
|
|
59
61
|
|
|
60
62
|
async def async_initialize(self) -> None:
|
|
@@ -63,7 +65,7 @@ class CamoufoxManager:
|
|
|
63
65
|
return
|
|
64
66
|
|
|
65
67
|
try:
|
|
66
|
-
emit_info("
|
|
68
|
+
emit_info("Initializing Camoufox (privacy Firefox)...")
|
|
67
69
|
|
|
68
70
|
# Ensure Camoufox binary and dependencies are fetched before launching
|
|
69
71
|
await self._prefetch_camoufox()
|
|
@@ -80,7 +82,7 @@ class CamoufoxManager:
|
|
|
80
82
|
|
|
81
83
|
async def _initialize_camoufox(self) -> None:
|
|
82
84
|
"""Try to start Camoufox with the configured privacy settings."""
|
|
83
|
-
emit_info(f"
|
|
85
|
+
emit_info(f"Using persistent profile: {self.profile_dir}")
|
|
84
86
|
# Lazy import camoufox to avoid triggering heavy optional deps at import time
|
|
85
87
|
try:
|
|
86
88
|
import camoufox
|
|
@@ -103,8 +105,8 @@ class CamoufoxManager:
|
|
|
103
105
|
except Exception:
|
|
104
106
|
from playwright.async_api import async_playwright
|
|
105
107
|
|
|
106
|
-
|
|
107
|
-
"
|
|
108
|
+
emit_warning(
|
|
109
|
+
"Camoufox no disponible. Usando Playwright (Chromium) como alternativa."
|
|
108
110
|
)
|
|
109
111
|
pw = await async_playwright().start()
|
|
110
112
|
# Use persistent context directory for Chromium to emulate previous behavior
|
|
@@ -142,9 +144,7 @@ class CamoufoxManager:
|
|
|
142
144
|
|
|
143
145
|
async def _prefetch_camoufox(self) -> None:
|
|
144
146
|
"""Prefetch Camoufox binary and dependencies."""
|
|
145
|
-
emit_info(
|
|
146
|
-
"[cyan]🔍 Ensuring Camoufox binary and dependencies are up-to-date...[/cyan]"
|
|
147
|
-
)
|
|
147
|
+
emit_info("Ensuring Camoufox binary and dependencies are up-to-date...")
|
|
148
148
|
|
|
149
149
|
# Lazy import camoufox utilities to avoid side effects during module import
|
|
150
150
|
try:
|
|
@@ -152,20 +152,20 @@ class CamoufoxManager:
|
|
|
152
152
|
from camoufox.locale import ALLOW_GEOIP, download_mmdb
|
|
153
153
|
from camoufox.pkgman import CamoufoxFetcher, camoufox_path
|
|
154
154
|
except Exception:
|
|
155
|
-
|
|
156
|
-
"
|
|
155
|
+
emit_warning(
|
|
156
|
+
"Camoufox no disponible. Omitiendo prefetch y preparándose para usar Playwright."
|
|
157
157
|
)
|
|
158
158
|
return
|
|
159
159
|
|
|
160
160
|
needs_install = False
|
|
161
161
|
try:
|
|
162
162
|
camoufox_path(download_if_missing=False)
|
|
163
|
-
emit_info("
|
|
163
|
+
emit_info("Using cached Camoufox installation")
|
|
164
164
|
except (CamoufoxNotInstalled, FileNotFoundError):
|
|
165
|
-
emit_info("
|
|
165
|
+
emit_info("Camoufox not found, installing fresh copy")
|
|
166
166
|
needs_install = True
|
|
167
167
|
except UnsupportedVersion:
|
|
168
|
-
emit_info("
|
|
168
|
+
emit_info("Camoufox update required, reinstalling")
|
|
169
169
|
needs_install = True
|
|
170
170
|
|
|
171
171
|
if needs_install:
|
|
@@ -175,7 +175,7 @@ class CamoufoxManager:
|
|
|
175
175
|
if ALLOW_GEOIP:
|
|
176
176
|
download_mmdb()
|
|
177
177
|
|
|
178
|
-
emit_info("
|
|
178
|
+
emit_info("Camoufox dependencies ready")
|
|
179
179
|
|
|
180
180
|
async def close_page(self, page: Page) -> None:
|
|
181
181
|
"""Close a specific page."""
|
|
@@ -195,13 +195,9 @@ class CamoufoxManager:
|
|
|
195
195
|
try:
|
|
196
196
|
storage_state_path = self.profile_dir / "storage_state.json"
|
|
197
197
|
await self._context.storage_state(path=str(storage_state_path))
|
|
198
|
-
|
|
199
|
-
f"[green]💾 Browser state saved to {storage_state_path}[/green]"
|
|
200
|
-
)
|
|
198
|
+
emit_success(f"Browser state saved to {storage_state_path}")
|
|
201
199
|
except Exception as e:
|
|
202
|
-
|
|
203
|
-
f"[yellow]Warning: Could not save storage state: {e}[/yellow]"
|
|
204
|
-
)
|
|
200
|
+
emit_warning(f"Could not save storage state: {e}")
|
|
205
201
|
|
|
206
202
|
await self._context.close()
|
|
207
203
|
self._context = None
|
|
@@ -210,12 +206,12 @@ class CamoufoxManager:
|
|
|
210
206
|
self._browser = None
|
|
211
207
|
self._initialized = False
|
|
212
208
|
except Exception as e:
|
|
213
|
-
|
|
209
|
+
emit_warning(f"Warning during cleanup: {e}")
|
|
214
210
|
|
|
215
211
|
async def close(self) -> None:
|
|
216
212
|
"""Close the browser and clean up resources."""
|
|
217
213
|
await self._cleanup()
|
|
218
|
-
emit_info("
|
|
214
|
+
emit_info("Camoufox browser closed")
|
|
219
215
|
|
|
220
216
|
def __del__(self):
|
|
221
217
|
"""Ensure cleanup on object destruction."""
|