code-puppy 0.0.175__tar.gz → 0.0.176__tar.gz

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.
Files changed (125) hide show
  1. {code_puppy-0.0.175 → code_puppy-0.0.176}/PKG-INFO +1 -1
  2. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/tools/browser/camoufox_manager.py +26 -42
  3. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/tools/browser/vqa_agent.py +0 -2
  4. {code_puppy-0.0.175 → code_puppy-0.0.176}/pyproject.toml +1 -1
  5. code_puppy-0.0.175/code_puppy/tools/camoufox_manager.py +0 -150
  6. {code_puppy-0.0.175 → code_puppy-0.0.176}/.gitignore +0 -0
  7. {code_puppy-0.0.175 → code_puppy-0.0.176}/LICENSE +0 -0
  8. {code_puppy-0.0.175 → code_puppy-0.0.176}/README.md +0 -0
  9. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/__init__.py +0 -0
  10. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/__main__.py +0 -0
  11. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/agents/__init__.py +0 -0
  12. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/agents/agent_code_puppy.py +0 -0
  13. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/agents/agent_creator_agent.py +0 -0
  14. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/agents/agent_manager.py +0 -0
  15. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/agents/agent_qa_kitten.py +0 -0
  16. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/agents/base_agent.py +0 -0
  17. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/agents/json_agent.py +0 -0
  18. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/callbacks.py +0 -0
  19. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/command_line/__init__.py +0 -0
  20. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/command_line/command_handler.py +0 -0
  21. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/command_line/file_path_completion.py +0 -0
  22. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/command_line/load_context_completion.py +0 -0
  23. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/command_line/mcp/__init__.py +0 -0
  24. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/command_line/mcp/add_command.py +0 -0
  25. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/command_line/mcp/base.py +0 -0
  26. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/command_line/mcp/handler.py +0 -0
  27. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/command_line/mcp/help_command.py +0 -0
  28. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/command_line/mcp/install_command.py +0 -0
  29. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/command_line/mcp/list_command.py +0 -0
  30. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/command_line/mcp/logs_command.py +0 -0
  31. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/command_line/mcp/remove_command.py +0 -0
  32. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/command_line/mcp/restart_command.py +0 -0
  33. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/command_line/mcp/search_command.py +0 -0
  34. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/command_line/mcp/start_all_command.py +0 -0
  35. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/command_line/mcp/start_command.py +0 -0
  36. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/command_line/mcp/status_command.py +0 -0
  37. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/command_line/mcp/stop_all_command.py +0 -0
  38. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/command_line/mcp/stop_command.py +0 -0
  39. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/command_line/mcp/test_command.py +0 -0
  40. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/command_line/mcp/utils.py +0 -0
  41. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/command_line/mcp/wizard_utils.py +0 -0
  42. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/command_line/model_picker_completion.py +0 -0
  43. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/command_line/motd.py +0 -0
  44. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/command_line/prompt_toolkit_completion.py +0 -0
  45. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/command_line/utils.py +0 -0
  46. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/config.py +0 -0
  47. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/http_utils.py +0 -0
  48. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/main.py +0 -0
  49. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/mcp_/__init__.py +0 -0
  50. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/mcp_/async_lifecycle.py +0 -0
  51. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/mcp_/blocking_startup.py +0 -0
  52. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/mcp_/captured_stdio_server.py +0 -0
  53. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/mcp_/circuit_breaker.py +0 -0
  54. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/mcp_/config_wizard.py +0 -0
  55. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/mcp_/dashboard.py +0 -0
  56. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/mcp_/error_isolation.py +0 -0
  57. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/mcp_/examples/retry_example.py +0 -0
  58. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/mcp_/health_monitor.py +0 -0
  59. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/mcp_/managed_server.py +0 -0
  60. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/mcp_/manager.py +0 -0
  61. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/mcp_/registry.py +0 -0
  62. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/mcp_/retry_manager.py +0 -0
  63. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/mcp_/server_registry_catalog.py +0 -0
  64. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/mcp_/status_tracker.py +0 -0
  65. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/mcp_/system_tools.py +0 -0
  66. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/messaging/__init__.py +0 -0
  67. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/messaging/message_queue.py +0 -0
  68. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/messaging/queue_console.py +0 -0
  69. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/messaging/renderers.py +0 -0
  70. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/messaging/spinner/__init__.py +0 -0
  71. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/messaging/spinner/console_spinner.py +0 -0
  72. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/messaging/spinner/spinner_base.py +0 -0
  73. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/messaging/spinner/textual_spinner.py +0 -0
  74. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/model_factory.py +0 -0
  75. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/models.json +0 -0
  76. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/plugins/__init__.py +0 -0
  77. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/reopenable_async_client.py +0 -0
  78. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/round_robin_model.py +0 -0
  79. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/status_display.py +0 -0
  80. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/summarization_agent.py +0 -0
  81. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/tools/__init__.py +0 -0
  82. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/tools/agent_tools.py +0 -0
  83. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/tools/browser/__init__.py +0 -0
  84. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/tools/browser/browser_control.py +0 -0
  85. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/tools/browser/browser_interactions.py +0 -0
  86. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/tools/browser/browser_locators.py +0 -0
  87. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/tools/browser/browser_navigation.py +0 -0
  88. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/tools/browser/browser_screenshot.py +0 -0
  89. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/tools/browser/browser_scripts.py +0 -0
  90. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/tools/browser/browser_workflows.py +0 -0
  91. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/tools/browser_control.py +0 -0
  92. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/tools/browser_interactions.py +0 -0
  93. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/tools/browser_locators.py +0 -0
  94. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/tools/browser_navigation.py +0 -0
  95. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/tools/browser_screenshot.py +0 -0
  96. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/tools/browser_scripts.py +0 -0
  97. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/tools/browser_workflows.py +0 -0
  98. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/tools/command_runner.py +0 -0
  99. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/tools/common.py +0 -0
  100. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/tools/file_modifications.py +0 -0
  101. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/tools/file_operations.py +0 -0
  102. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/tools/tools_content.py +0 -0
  103. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/tui/__init__.py +0 -0
  104. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/tui/app.py +0 -0
  105. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/tui/components/__init__.py +0 -0
  106. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/tui/components/chat_view.py +0 -0
  107. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/tui/components/command_history_modal.py +0 -0
  108. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/tui/components/copy_button.py +0 -0
  109. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/tui/components/custom_widgets.py +0 -0
  110. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/tui/components/human_input_modal.py +0 -0
  111. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/tui/components/input_area.py +0 -0
  112. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/tui/components/sidebar.py +0 -0
  113. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/tui/components/status_bar.py +0 -0
  114. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/tui/messages.py +0 -0
  115. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/tui/models/__init__.py +0 -0
  116. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/tui/models/chat_message.py +0 -0
  117. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/tui/models/command_history.py +0 -0
  118. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/tui/models/enums.py +0 -0
  119. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/tui/screens/__init__.py +0 -0
  120. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/tui/screens/help.py +0 -0
  121. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/tui/screens/mcp_install_wizard.py +0 -0
  122. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/tui/screens/settings.py +0 -0
  123. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/tui/screens/tools.py +0 -0
  124. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/tui_state.py +0 -0
  125. {code_puppy-0.0.175 → code_puppy-0.0.176}/code_puppy/version_checker.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code-puppy
3
- Version: 0.0.175
3
+ Version: 0.0.176
4
4
  Summary: Code generation agent
5
5
  Project-URL: repository, https://github.com/mpfaffenberger/code_puppy
6
6
  Project-URL: HomePage, https://github.com/mpfaffenberger/code_puppy
@@ -3,12 +3,13 @@
3
3
  from typing import Optional
4
4
 
5
5
  import camoufox
6
- from playwright.async_api import Browser, BrowserContext, Page, Playwright, async_playwright
6
+ from playwright.async_api import Browser, BrowserContext, Page
7
7
 
8
8
  from code_puppy.messaging import emit_info
9
- from camoufox.pkgman import CamoufoxFetcher
9
+ from camoufox.pkgman import CamoufoxFetcher, camoufox_path
10
10
  from camoufox.locale import ALLOW_GEOIP, download_mmdb
11
- from camoufox.addons import maybe_download_addons, DefaultAddons
11
+ from camoufox.addons import DefaultAddons
12
+ from camoufox.exceptions import CamoufoxNotInstalled, UnsupportedVersion
12
13
 
13
14
 
14
15
  class CamoufoxManager:
@@ -17,7 +18,6 @@ class CamoufoxManager:
17
18
  _instance: Optional["CamoufoxManager"] = None
18
19
  _browser: Optional[Browser] = None
19
20
  _context: Optional[BrowserContext] = None
20
- _playwright: Optional[Playwright] = None
21
21
  _initialized: bool = False
22
22
 
23
23
  def __new__(cls):
@@ -56,23 +56,13 @@ class CamoufoxManager:
56
56
  # Ensure Camoufox binary and dependencies are fetched before launching
57
57
  await self._prefetch_camoufox()
58
58
 
59
- try:
60
- await self._initialize_camoufox()
61
- emit_info(
62
- "[green]✅ Camoufox initialized successfully (privacy-focused Firefox)[/green]"
63
- )
64
- except Exception as camoufox_error:
65
- error_reason = str(camoufox_error).splitlines()[0]
66
- emit_info(
67
- "[yellow]⚠️ Camoufox failed to initialize, falling back to Playwright Firefox[/yellow]"
68
- )
69
- await self._cleanup()
70
- await self._initialize_playwright_firefox(error_reason)
71
-
59
+ await self._initialize_camoufox()
60
+ emit_info(
61
+ "[green]✅ Camoufox initialized successfully (privacy-focused Firefox)[/green]"
62
+ )
72
63
  self._initialized = True
73
64
 
74
65
  except Exception as e:
75
- emit_info(f"[red]❌ Failed to initialize browser: {e}[/red]")
76
66
  await self._cleanup()
77
67
  raise
78
68
 
@@ -82,6 +72,8 @@ class CamoufoxManager:
82
72
  headless=self.headless,
83
73
  block_webrtc=self.block_webrtc,
84
74
  humanize=self.humanize,
75
+ exclude_addons=list(DefaultAddons),
76
+ addons=[],
85
77
  )
86
78
  self._browser = await camoufox_instance.start()
87
79
  self._context = await self._browser.new_context(
@@ -91,19 +83,6 @@ class CamoufoxManager:
91
83
  page = await self._context.new_page()
92
84
  await page.goto(self.homepage)
93
85
 
94
- async def _initialize_playwright_firefox(self, error_reason: str) -> None:
95
- """Fallback to vanilla Playwright Firefox when Camoufox fails."""
96
- self._playwright = await async_playwright().start()
97
- self._browser = await self._playwright.firefox.launch(headless=self.headless)
98
- self._context = await self._browser.new_context(
99
- viewport={"width": 1920, "height": 1080},
100
- ignore_https_errors=True,
101
- )
102
- page = await self._context.new_page()
103
- await page.goto(self.homepage)
104
- emit_info(
105
- f"[green]✅ Playwright Firefox fallback ready (Camoufox error: {error_reason})[/green]"
106
- )
107
86
 
108
87
  async def get_current_page(self) -> Optional[Page]:
109
88
  """Get the currently active page."""
@@ -128,17 +107,25 @@ class CamoufoxManager:
128
107
  async def _prefetch_camoufox(self) -> None:
129
108
  """Prefetch Camoufox binary and dependencies."""
130
109
  emit_info("[cyan]🔍 Ensuring Camoufox binary and dependencies are up-to-date...[/cyan]")
131
-
132
- # Fetch Camoufox binary if needed
133
- CamoufoxFetcher().install()
134
-
110
+
111
+ needs_install = False
112
+ try:
113
+ camoufox_path(download_if_missing=False)
114
+ emit_info("[cyan]🗃️ Using cached Camoufox installation[/cyan]")
115
+ except (CamoufoxNotInstalled, FileNotFoundError):
116
+ emit_info("[cyan]📥 Camoufox not found, installing fresh copy[/cyan]")
117
+ needs_install = True
118
+ except UnsupportedVersion:
119
+ emit_info("[cyan]♻️ Camoufox update required, reinstalling[/cyan]")
120
+ needs_install = True
121
+
122
+ if needs_install:
123
+ CamoufoxFetcher().install()
124
+
135
125
  # Fetch GeoIP database if enabled
136
126
  if ALLOW_GEOIP:
137
127
  download_mmdb()
138
-
139
- # Download default addons
140
- maybe_download_addons(list(DefaultAddons))
141
-
128
+
142
129
  emit_info("[cyan]📦 Camoufox dependencies ready[/cyan]")
143
130
 
144
131
  async def close_page(self, page: Page) -> None:
@@ -160,9 +147,6 @@ class CamoufoxManager:
160
147
  if self._browser:
161
148
  await self._browser.close()
162
149
  self._browser = None
163
- if self._playwright:
164
- await self._playwright.stop()
165
- self._playwright = None
166
150
  self._initialized = False
167
151
  except Exception as e:
168
152
  emit_info(f"[yellow]Warning during cleanup: {e}[/yellow]")
@@ -3,7 +3,6 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  from functools import lru_cache
6
- from typing import Optional
7
6
 
8
7
  from pydantic import BaseModel, Field
9
8
  from pydantic_ai import Agent, BinaryContent
@@ -37,7 +36,6 @@ def _load_vqa_agent(model_name: str) -> Agent[None, VisualAnalysisResult]:
37
36
  instructions=instructions,
38
37
  output_type=VisualAnalysisResult,
39
38
  retries=2,
40
- instrument=instrumentation,
41
39
  )
42
40
 
43
41
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "code-puppy"
7
- version = "0.0.175"
7
+ version = "0.0.176"
8
8
  description = "Code generation agent"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -1,150 +0,0 @@
1
- """Camoufox browser manager - privacy-focused Firefox automation."""
2
-
3
- from typing import Optional
4
-
5
- import camoufox
6
- from playwright.async_api import Browser, BrowserContext, Page
7
-
8
- from code_puppy.messaging import emit_info
9
-
10
-
11
- class CamoufoxManager:
12
- """Singleton browser manager for Camoufox (privacy-focused Firefox) automation."""
13
-
14
- _instance: Optional["CamoufoxManager"] = None
15
- _browser: Optional[Browser] = None
16
- _context: Optional[BrowserContext] = None
17
- _initialized: bool = False
18
-
19
- def __new__(cls):
20
- if cls._instance is None:
21
- cls._instance = super().__new__(cls)
22
- return cls._instance
23
-
24
- def __init__(self):
25
- # Only initialize once
26
- if hasattr(self, "_init_done"):
27
- return
28
- self._init_done = True
29
-
30
- self.headless = False
31
- self.homepage = "https://www.google.com"
32
- # Camoufox-specific settings
33
- self.geoip = True # Enable GeoIP spoofing
34
- self.block_webrtc = True # Block WebRTC for privacy
35
- self.humanize = True # Add human-like behavior
36
-
37
- @classmethod
38
- def get_instance(cls) -> "CamoufoxManager":
39
- """Get the singleton instance."""
40
- if cls._instance is None:
41
- cls._instance = cls()
42
- return cls._instance
43
-
44
- async def async_initialize(self) -> None:
45
- """Initialize Camoufox browser."""
46
- if self._initialized:
47
- return
48
-
49
- try:
50
- emit_info("[yellow]Initializing Camoufox (privacy Firefox)...[/yellow]")
51
-
52
- # Launch Camoufox with basic privacy settings
53
- # Note: Many advanced features require additional packages or are handled internally
54
- camoufox_instance = camoufox.AsyncCamoufox(
55
- headless=self.headless,
56
- # Only using well-supported basic options
57
- block_webrtc=self.block_webrtc,
58
- humanize=self.humanize,
59
- # Let camoufox handle other privacy settings automatically
60
- )
61
- self._browser = await camoufox_instance.start()
62
-
63
- # Create context (Camoufox handles most privacy settings automatically)
64
- self._context = await self._browser.new_context(
65
- viewport={"width": 1920, "height": 1080},
66
- ignore_https_errors=True,
67
- )
68
-
69
- # Create initial page and navigate to homepage
70
- page = await self._context.new_page()
71
- await page.goto(self.homepage)
72
-
73
- self._initialized = True
74
- emit_info(
75
- "[green]✅ Camoufox initialized successfully (privacy-focused Firefox)[/green]"
76
- )
77
-
78
- except Exception as e:
79
- emit_info(f"[red]❌ Failed to initialize Camoufox: {e}[/red]")
80
- await self._cleanup()
81
- raise
82
-
83
- async def get_current_page(self) -> Optional[Page]:
84
- """Get the currently active page."""
85
- if not self._initialized or not self._context:
86
- await self.async_initialize()
87
-
88
- if self._context:
89
- pages = self._context.pages
90
- return pages[0] if pages else None
91
- return None
92
-
93
- async def new_page(self, url: Optional[str] = None) -> Page:
94
- """Create a new page and optionally navigate to URL."""
95
- if not self._initialized:
96
- await self.async_initialize()
97
-
98
- page = await self._context.new_page()
99
- if url:
100
- await page.goto(url)
101
- return page
102
-
103
- async def close_page(self, page: Page) -> None:
104
- """Close a specific page."""
105
- await page.close()
106
-
107
- async def get_all_pages(self) -> list[Page]:
108
- """Get all open pages."""
109
- if not self._context:
110
- return []
111
- return self._context.pages
112
-
113
- async def _cleanup(self) -> None:
114
- """Clean up browser resources."""
115
- try:
116
- if self._context:
117
- await self._context.close()
118
- self._context = None
119
- if self._browser:
120
- await self._browser.close()
121
- self._browser = None
122
- self._initialized = False
123
- except Exception as e:
124
- emit_info(f"[yellow]Warning during cleanup: {e}[/yellow]")
125
-
126
- async def close(self) -> None:
127
- """Close the browser and clean up resources."""
128
- await self._cleanup()
129
- emit_info("[yellow]Camoufox browser closed[/yellow]")
130
-
131
- def __del__(self):
132
- """Ensure cleanup on object destruction."""
133
- # Note: Can't use async in __del__, so this is just a fallback
134
- if self._initialized:
135
- import asyncio
136
-
137
- try:
138
- loop = asyncio.get_event_loop()
139
- if loop.is_running():
140
- loop.create_task(self._cleanup())
141
- else:
142
- loop.run_until_complete(self._cleanup())
143
- except:
144
- pass # Best effort cleanup
145
-
146
-
147
- # Convenience function for getting the singleton instance
148
- def get_camoufox_manager() -> CamoufoxManager:
149
- """Get the singleton CamoufoxManager instance."""
150
- return CamoufoxManager.get_instance()
File without changes
File without changes
File without changes