code-puppy 0.0.201__tar.gz → 0.0.203__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 (130) hide show
  1. {code_puppy-0.0.201 → code_puppy-0.0.203}/PKG-INFO +2 -2
  2. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/agents/agent_manager.py +4 -2
  3. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/agents/base_agent.py +2 -2
  4. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/command_line/attachments.py +2 -14
  5. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/tools/browser/camoufox_manager.py +52 -14
  6. {code_puppy-0.0.201 → code_puppy-0.0.203}/pyproject.toml +2 -2
  7. {code_puppy-0.0.201 → code_puppy-0.0.203}/.gitignore +0 -0
  8. {code_puppy-0.0.201 → code_puppy-0.0.203}/LICENSE +0 -0
  9. {code_puppy-0.0.201 → code_puppy-0.0.203}/README.md +0 -0
  10. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/__init__.py +0 -0
  11. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/__main__.py +0 -0
  12. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/agents/__init__.py +0 -0
  13. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/agents/agent_c_reviewer.py +0 -0
  14. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/agents/agent_code_puppy.py +0 -0
  15. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/agents/agent_code_reviewer.py +0 -0
  16. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/agents/agent_cpp_reviewer.py +0 -0
  17. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/agents/agent_creator_agent.py +0 -0
  18. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/agents/agent_golang_reviewer.py +0 -0
  19. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/agents/agent_javascript_reviewer.py +0 -0
  20. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/agents/agent_python_reviewer.py +0 -0
  21. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/agents/agent_qa_expert.py +0 -0
  22. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/agents/agent_qa_kitten.py +0 -0
  23. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/agents/agent_security_auditor.py +0 -0
  24. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/agents/agent_typescript_reviewer.py +0 -0
  25. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/agents/json_agent.py +0 -0
  26. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/callbacks.py +0 -0
  27. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/command_line/__init__.py +0 -0
  28. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/command_line/command_handler.py +0 -0
  29. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/command_line/file_path_completion.py +0 -0
  30. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/command_line/load_context_completion.py +0 -0
  31. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/command_line/mcp/__init__.py +0 -0
  32. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/command_line/mcp/add_command.py +0 -0
  33. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/command_line/mcp/base.py +0 -0
  34. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/command_line/mcp/handler.py +0 -0
  35. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/command_line/mcp/help_command.py +0 -0
  36. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/command_line/mcp/install_command.py +0 -0
  37. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/command_line/mcp/list_command.py +0 -0
  38. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/command_line/mcp/logs_command.py +0 -0
  39. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/command_line/mcp/remove_command.py +0 -0
  40. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/command_line/mcp/restart_command.py +0 -0
  41. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/command_line/mcp/search_command.py +0 -0
  42. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/command_line/mcp/start_all_command.py +0 -0
  43. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/command_line/mcp/start_command.py +0 -0
  44. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/command_line/mcp/status_command.py +0 -0
  45. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/command_line/mcp/stop_all_command.py +0 -0
  46. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/command_line/mcp/stop_command.py +0 -0
  47. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/command_line/mcp/test_command.py +0 -0
  48. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/command_line/mcp/utils.py +0 -0
  49. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/command_line/mcp/wizard_utils.py +0 -0
  50. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/command_line/model_picker_completion.py +0 -0
  51. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/command_line/motd.py +0 -0
  52. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/command_line/prompt_toolkit_completion.py +0 -0
  53. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/command_line/utils.py +0 -0
  54. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/config.py +0 -0
  55. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/http_utils.py +0 -0
  56. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/main.py +0 -0
  57. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/mcp_/__init__.py +0 -0
  58. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/mcp_/async_lifecycle.py +0 -0
  59. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/mcp_/blocking_startup.py +0 -0
  60. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/mcp_/captured_stdio_server.py +0 -0
  61. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/mcp_/circuit_breaker.py +0 -0
  62. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/mcp_/config_wizard.py +0 -0
  63. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/mcp_/dashboard.py +0 -0
  64. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/mcp_/error_isolation.py +0 -0
  65. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/mcp_/examples/retry_example.py +0 -0
  66. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/mcp_/health_monitor.py +0 -0
  67. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/mcp_/managed_server.py +0 -0
  68. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/mcp_/manager.py +0 -0
  69. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/mcp_/registry.py +0 -0
  70. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/mcp_/retry_manager.py +0 -0
  71. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/mcp_/server_registry_catalog.py +0 -0
  72. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/mcp_/status_tracker.py +0 -0
  73. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/mcp_/system_tools.py +0 -0
  74. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/messaging/__init__.py +0 -0
  75. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/messaging/message_queue.py +0 -0
  76. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/messaging/queue_console.py +0 -0
  77. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/messaging/renderers.py +0 -0
  78. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/messaging/spinner/__init__.py +0 -0
  79. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/messaging/spinner/console_spinner.py +0 -0
  80. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/messaging/spinner/spinner_base.py +0 -0
  81. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/messaging/spinner/textual_spinner.py +0 -0
  82. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/model_factory.py +0 -0
  83. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/models.json +0 -0
  84. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/plugins/__init__.py +0 -0
  85. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/plugins/example_custom_command/register_callbacks.py +0 -0
  86. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/reopenable_async_client.py +0 -0
  87. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/round_robin_model.py +0 -0
  88. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/session_storage.py +0 -0
  89. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/status_display.py +0 -0
  90. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/summarization_agent.py +0 -0
  91. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/tools/__init__.py +0 -0
  92. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/tools/agent_tools.py +0 -0
  93. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/tools/browser/__init__.py +0 -0
  94. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/tools/browser/browser_control.py +0 -0
  95. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/tools/browser/browser_interactions.py +0 -0
  96. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/tools/browser/browser_locators.py +0 -0
  97. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/tools/browser/browser_navigation.py +0 -0
  98. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/tools/browser/browser_screenshot.py +0 -0
  99. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/tools/browser/browser_scripts.py +0 -0
  100. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/tools/browser/browser_workflows.py +0 -0
  101. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/tools/browser/vqa_agent.py +0 -0
  102. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/tools/command_runner.py +0 -0
  103. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/tools/common.py +0 -0
  104. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/tools/file_modifications.py +0 -0
  105. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/tools/file_operations.py +0 -0
  106. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/tools/tools_content.py +0 -0
  107. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/tui/__init__.py +0 -0
  108. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/tui/app.py +0 -0
  109. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/tui/components/__init__.py +0 -0
  110. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/tui/components/chat_view.py +0 -0
  111. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/tui/components/command_history_modal.py +0 -0
  112. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/tui/components/copy_button.py +0 -0
  113. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/tui/components/custom_widgets.py +0 -0
  114. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/tui/components/human_input_modal.py +0 -0
  115. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/tui/components/input_area.py +0 -0
  116. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/tui/components/sidebar.py +0 -0
  117. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/tui/components/status_bar.py +0 -0
  118. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/tui/messages.py +0 -0
  119. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/tui/models/__init__.py +0 -0
  120. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/tui/models/chat_message.py +0 -0
  121. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/tui/models/command_history.py +0 -0
  122. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/tui/models/enums.py +0 -0
  123. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/tui/screens/__init__.py +0 -0
  124. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/tui/screens/autosave_picker.py +0 -0
  125. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/tui/screens/help.py +0 -0
  126. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/tui/screens/mcp_install_wizard.py +0 -0
  127. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/tui/screens/settings.py +0 -0
  128. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/tui/screens/tools.py +0 -0
  129. {code_puppy-0.0.201 → code_puppy-0.0.203}/code_puppy/tui_state.py +0 -0
  130. {code_puppy-0.0.201 → code_puppy-0.0.203}/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.201
3
+ Version: 0.0.203
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
@@ -26,7 +26,7 @@ Requires-Dist: openai>=1.99.1
26
26
  Requires-Dist: pathspec>=0.11.0
27
27
  Requires-Dist: playwright>=1.40.0
28
28
  Requires-Dist: prompt-toolkit>=3.0.52
29
- Requires-Dist: pydantic-ai==1.0.6
29
+ Requires-Dist: pydantic-ai==1.0.5
30
30
  Requires-Dist: pydantic>=2.4.0
31
31
  Requires-Dist: pyjwt>=2.8.0
32
32
  Requires-Dist: pytest-cov>=6.1.1
@@ -252,7 +252,8 @@ def set_current_agent(agent_name: str) -> bool:
252
252
  global _CURRENT_AGENT
253
253
  curr_agent = get_current_agent()
254
254
  if curr_agent is not None:
255
- _AGENT_HISTORIES[curr_agent.name] = curr_agent.get_message_history()
255
+ # Store a shallow copy so future mutations don't affect saved history
256
+ _AGENT_HISTORIES[curr_agent.name] = list(curr_agent.get_message_history())
256
257
  # Generate a message group ID for agent switching
257
258
  message_group_id = str(uuid.uuid4())
258
259
  _discover_agents(message_group_id=message_group_id)
@@ -269,7 +270,8 @@ def set_current_agent(agent_name: str) -> bool:
269
270
  _SESSION_AGENTS_CACHE[session_id] = agent_name
270
271
  _save_session_data(_SESSION_AGENTS_CACHE)
271
272
  if agent_obj.name in _AGENT_HISTORIES:
272
- agent_obj.set_message_history(_AGENT_HISTORIES[agent_obj.name])
273
+ # Restore a copy to avoid sharing the same list instance
274
+ agent_obj.set_message_history(list(_AGENT_HISTORIES[agent_obj.name]))
273
275
  on_agent_reload(agent_obj.id, agent_name)
274
276
  return True
275
277
 
@@ -13,7 +13,7 @@ import pydantic
13
13
  import pydantic_ai.models
14
14
  from pydantic_ai import Agent as PydanticAgent
15
15
  from pydantic_ai import BinaryContent, DocumentUrl, ImageUrl
16
- from pydantic_ai import RunContext, UsageLimitExceeded
16
+ from pydantic_ai import RunContext, UsageLimitExceeded, UsageLimits
17
17
  from pydantic_ai.messages import (
18
18
  ModelMessage,
19
19
  ModelRequest,
@@ -950,7 +950,7 @@ class BaseAgent(ABC):
950
950
  self.set_message_history(
951
951
  self.prune_interrupted_tool_calls(self.get_message_history())
952
952
  )
953
- usage_limits = pydantic_ai.agent._usage.UsageLimits(request_limit=get_message_limit())
953
+ usage_limits = UsageLimits(request_limit=get_message_limit())
954
954
  result_ = await pydantic_agent.run(
955
955
  prompt_payload,
956
956
  message_history=self.get_message_history(),
@@ -151,20 +151,8 @@ def _is_supported_extension(path: Path) -> bool:
151
151
 
152
152
 
153
153
  def _parse_link(token: str) -> PromptLinkAttachment | None:
154
- if "://" not in token:
155
- return None
156
- scheme = token.split(":", 1)[0].lower()
157
- if scheme not in SUPPORTED_INLINE_SCHEMES:
158
- return None
159
- if token.lower().endswith(".pdf"):
160
- return PromptLinkAttachment(
161
- placeholder=token,
162
- url_part=DocumentUrl(url=token),
163
- )
164
- return PromptLinkAttachment(
165
- placeholder=token,
166
- url_part=ImageUrl(url=token),
167
- )
154
+ """URL parsing disabled: no URLs are treated as attachments."""
155
+ return None
168
156
 
169
157
 
170
158
  @dataclass
@@ -1,6 +1,7 @@
1
1
  """Camoufox browser manager - privacy-focused Firefox automation."""
2
2
 
3
- from typing import Optional
3
+ from pathlib import Path
4
+ from typing import Optional, TypeAlias
4
5
 
5
6
  import camoufox
6
7
  from camoufox.addons import DefaultAddons
@@ -9,6 +10,8 @@ from camoufox.locale import ALLOW_GEOIP, download_mmdb
9
10
  from camoufox.pkgman import CamoufoxFetcher, camoufox_path
10
11
  from playwright.async_api import Browser, BrowserContext, Page
11
12
 
13
+ _MIN_VIEWPORT_DIMENSION = 640
14
+
12
15
  from code_puppy.messaging import emit_info
13
16
 
14
17
 
@@ -38,6 +41,9 @@ class CamoufoxManager:
38
41
  self.block_webrtc = True # Block WebRTC for privacy
39
42
  self.humanize = True # Add human-like behavior
40
43
 
44
+ # Persistent profile directory for consistent browser state across runs
45
+ self.profile_dir = self._get_profile_directory()
46
+
41
47
  @classmethod
42
48
  def get_instance(cls) -> "CamoufoxManager":
43
49
  """Get the singleton instance."""
@@ -45,6 +51,16 @@ class CamoufoxManager:
45
51
  cls._instance = cls()
46
52
  return cls._instance
47
53
 
54
+ def _get_profile_directory(self) -> Path:
55
+ """Get or create the persistent profile directory.
56
+
57
+ Returns a Path object pointing to ~/.code_puppy/camoufox_profile
58
+ where browser data (cookies, history, bookmarks, etc.) will be stored.
59
+ """
60
+ profile_path = Path.home() / ".code_puppy" / "camoufox_profile"
61
+ profile_path.mkdir(parents=True, exist_ok=True)
62
+ return profile_path
63
+
48
64
  async def async_initialize(self) -> None:
49
65
  """Initialize Camoufox browser."""
50
66
  if self._initialized:
@@ -68,30 +84,40 @@ class CamoufoxManager:
68
84
 
69
85
  async def _initialize_camoufox(self) -> None:
70
86
  """Try to start Camoufox with the configured privacy settings."""
87
+ emit_info(f"[cyan]📁 Using persistent profile: {self.profile_dir}[/cyan]")
88
+
71
89
  camoufox_instance = camoufox.AsyncCamoufox(
72
90
  headless=self.headless,
73
91
  block_webrtc=self.block_webrtc,
74
92
  humanize=self.humanize,
75
93
  exclude_addons=list(DefaultAddons),
94
+ persistent_context=True,
95
+ user_data_dir=str(self.profile_dir),
76
96
  addons=[],
77
97
  )
78
- self._browser = await camoufox_instance.start()
79
- self._context = await self._browser.new_context(
80
- viewport={"width": 1920, "height": 1080},
81
- ignore_https_errors=True,
82
- )
83
- page = await self._context.new_page()
84
- await page.goto(self.homepage)
98
+
99
+ self._browser = camoufox_instance.browser
100
+ # Use persistent storage directory for browser context
101
+ # This ensures cookies, localStorage, history, etc. persist across runs
102
+ if not self._initialized:
103
+ self._context = await camoufox_instance.start()
104
+ self._initialized = True
105
+ # Do not auto-open a page here to avoid duplicate windows/tabs.
85
106
 
86
107
  async def get_current_page(self) -> Optional[Page]:
87
- """Get the currently active page."""
108
+ """Get the currently active page. Lazily creates one if none exist."""
88
109
  if not self._initialized or not self._context:
89
110
  await self.async_initialize()
90
111
 
91
- if self._context:
92
- pages = self._context.pages
93
- return pages[0] if pages else None
94
- return None
112
+ if not self._context:
113
+ return None
114
+
115
+ pages = self._context.pages
116
+ if pages:
117
+ return pages[0]
118
+
119
+ # Lazily create a new blank page without navigation
120
+ return await self._context.new_page()
95
121
 
96
122
  async def new_page(self, url: Optional[str] = None) -> Page:
97
123
  """Create a new page and optionally navigate to URL."""
@@ -140,9 +166,21 @@ class CamoufoxManager:
140
166
  return self._context.pages
141
167
 
142
168
  async def _cleanup(self) -> None:
143
- """Clean up browser resources."""
169
+ """Clean up browser resources and save persistent state."""
144
170
  try:
171
+ # Save browser state before closing (cookies, localStorage, etc.)
145
172
  if self._context:
173
+ try:
174
+ storage_state_path = self.profile_dir / "storage_state.json"
175
+ await self._context.storage_state(path=str(storage_state_path))
176
+ emit_info(
177
+ f"[green]💾 Browser state saved to {storage_state_path}[/green]"
178
+ )
179
+ except Exception as e:
180
+ emit_info(
181
+ f"[yellow]Warning: Could not save storage state: {e}[/yellow]"
182
+ )
183
+
146
184
  await self._context.close()
147
185
  self._context = None
148
186
  if self._browser:
@@ -4,12 +4,12 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "code-puppy"
7
- version = "0.0.201"
7
+ version = "0.0.203"
8
8
  description = "Code generation agent"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
11
11
  dependencies = [
12
- "pydantic-ai==1.0.6",
12
+ "pydantic-ai==1.0.5",
13
13
  "httpx>=0.24.1",
14
14
  "rich>=13.4.2",
15
15
  "logfire>=0.7.1",
File without changes
File without changes
File without changes