code-puppy 0.0.175__py3-none-any.whl → 0.0.176__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/tools/browser/camoufox_manager.py +26 -42
- code_puppy/tools/browser/vqa_agent.py +0 -2
- {code_puppy-0.0.175.dist-info → code_puppy-0.0.176.dist-info}/METADATA +1 -1
- {code_puppy-0.0.175.dist-info → code_puppy-0.0.176.dist-info}/RECORD +8 -9
- code_puppy/tools/camoufox_manager.py +0 -150
- {code_puppy-0.0.175.data → code_puppy-0.0.176.data}/data/code_puppy/models.json +0 -0
- {code_puppy-0.0.175.dist-info → code_puppy-0.0.176.dist-info}/WHEEL +0 -0
- {code_puppy-0.0.175.dist-info → code_puppy-0.0.176.dist-info}/entry_points.txt +0 -0
- {code_puppy-0.0.175.dist-info → code_puppy-0.0.176.dist-info}/licenses/LICENSE +0 -0
@@ -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
|
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
|
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
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
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
|
-
|
133
|
-
|
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
|
|
@@ -81,7 +81,6 @@ code_puppy/tools/browser_navigation.py,sha256=Tj_fNcM3KGpkM2UTKcGQX8BpI373Sv7xZA
|
|
81
81
|
code_puppy/tools/browser_screenshot.py,sha256=QSwxS37G4LSo-Q9SBiuIofxWKnyInM90TY-_fiWQLrs,9222
|
82
82
|
code_puppy/tools/browser_scripts.py,sha256=BLSx1Q2F_mOOoGCoyXat3HvazTb1XaFYPXAF8CYVeX8,15071
|
83
83
|
code_puppy/tools/browser_workflows.py,sha256=4u4u59arpY65hdcDMvJGpT02vks0ufnXNJVujzKe_dg,6430
|
84
|
-
code_puppy/tools/camoufox_manager.py,sha256=bYnwyOETGDe_h8Q3kXH7w6kFr-OCpMi_Zuh4y11p__E,5097
|
85
84
|
code_puppy/tools/command_runner.py,sha256=5H4wK-v3UQ713_0JRefpAwyxGBWD9R5yLptR6BhZyIY,22417
|
86
85
|
code_puppy/tools/common.py,sha256=pL-9xcRs3rxU7Fl9X9EUgbDp2-csh2LLJ5DHH_KAHKY,10596
|
87
86
|
code_puppy/tools/file_modifications.py,sha256=EaDWcv6gi8wAvpgyeJdKSKPWg9fTpZoEkxQiLCE6rn4,23218
|
@@ -95,8 +94,8 @@ code_puppy/tools/browser/browser_navigation.py,sha256=Tj_fNcM3KGpkM2UTKcGQX8BpI3
|
|
95
94
|
code_puppy/tools/browser/browser_screenshot.py,sha256=zbOMmN9_9aVDWJkC54-3zv4OEGJMs7LpveTc6JTLcxg,8275
|
96
95
|
code_puppy/tools/browser/browser_scripts.py,sha256=BLSx1Q2F_mOOoGCoyXat3HvazTb1XaFYPXAF8CYVeX8,15071
|
97
96
|
code_puppy/tools/browser/browser_workflows.py,sha256=HZ0lPmEyAobPIWR-SK1E0ngW1OfULLqw8XILVT4N8Fg,5979
|
98
|
-
code_puppy/tools/browser/camoufox_manager.py,sha256=
|
99
|
-
code_puppy/tools/browser/vqa_agent.py,sha256=
|
97
|
+
code_puppy/tools/browser/camoufox_manager.py,sha256=2Pl_UjPA7c413GLk0YZNfA83H09qc-lq7WfWfD6VxL4,6092
|
98
|
+
code_puppy/tools/browser/vqa_agent.py,sha256=0GMDgJAK728rIuSQxAVytFSNagjo0LCjCUxBTm3w9Po,1952
|
100
99
|
code_puppy/tui/__init__.py,sha256=XesAxIn32zLPOmvpR2wIDxDAnnJr81a5pBJB4cZp1Xs,321
|
101
100
|
code_puppy/tui/app.py,sha256=gZvg7ZOUp3JurZyKZ7ehpHEhmyC0NnfmQYWF_yZm36o,38163
|
102
101
|
code_puppy/tui/messages.py,sha256=zQoToWI0eWdT36NEsY6RdCFzcDfAmfvoPlHv8jiCbgo,720
|
@@ -118,9 +117,9 @@ code_puppy/tui/screens/help.py,sha256=eJuPaOOCp7ZSUlecearqsuX6caxWv7NQszUh0tZJjB
|
|
118
117
|
code_puppy/tui/screens/mcp_install_wizard.py,sha256=vObpQwLbXjQsxmSg-WCasoev1usEi0pollKnL0SHu9U,27693
|
119
118
|
code_puppy/tui/screens/settings.py,sha256=W22sevojC1_HZBoeoJTH3HWkehini3bGi_ic0OKgeLI,10685
|
120
119
|
code_puppy/tui/screens/tools.py,sha256=3pr2Xkpa9Js6Yhf1A3_wQVRzFOui-KDB82LwrsdBtyk,1715
|
121
|
-
code_puppy-0.0.
|
122
|
-
code_puppy-0.0.
|
123
|
-
code_puppy-0.0.
|
124
|
-
code_puppy-0.0.
|
125
|
-
code_puppy-0.0.
|
126
|
-
code_puppy-0.0.
|
120
|
+
code_puppy-0.0.176.data/data/code_puppy/models.json,sha256=iXmLZGflnQcu2DRh4WUlgAhoXdvoxUc7KBhB8YxawXM,3088
|
121
|
+
code_puppy-0.0.176.dist-info/METADATA,sha256=DdACj0Hz-4JSKT6RCDHyLbIjusHHJ1QTDpDio-dDKa4,20103
|
122
|
+
code_puppy-0.0.176.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
123
|
+
code_puppy-0.0.176.dist-info/entry_points.txt,sha256=Tp4eQC99WY3HOKd3sdvb22vZODRq0XkZVNpXOag_KdI,91
|
124
|
+
code_puppy-0.0.176.dist-info/licenses/LICENSE,sha256=31u8x0SPgdOq3izJX41kgFazWsM43zPEF9eskzqbJMY,1075
|
125
|
+
code_puppy-0.0.176.dist-info/RECORD,,
|
@@ -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
|
File without changes
|